I'm making an html editor component for an app (using UIWebView with contentEditable in iOS 5.0), and got stuck at how to handle UIWebView first responder status
[webView isFirstResponder], [webView becomeFirstResponder] and [webView resignFirstResponder] don't seem to work, and i've no idea how to make the webView become or resign it by code
If anyone knows how to work this out i would be very grateful, thanks in advance!
Here is how I overwrite these methods in a UIWebView subclass (content is the id of the editable element):
-(BOOL)resignFirstResponder {
[self setUserInteractionEnabled:NO];[self setUserInteractionEnabled:YES];
return [super resignFirstResponder];
}
// only works on iOS 6+
-(void)becomeFirstResponder {
self.keyboardDisplayRequiresUserAction = NO; // set here or during initialization
// important note: in some situations (newer iOS versions), it is also required to first call `blur()` on the 'content' element, otherwise the keyboard won't show up as expected
[self stringByEvaluatingJavaScriptFromString:#"document.getElementById('content').focus()"];
}
-(BOOL)isFirstResponder{
if ([[self stringByEvaluatingJavaScriptFromString:#"document.activeElement.id=='content'"] isEqualToString:#"true"]) {
return YES;
}
else {
return NO;
}
}
isFirstResponder will only return true after the keyboard is shown (e.g, it will return false at UIKeyboardWillShowNotification)
In case this is an issue, another way to check if the UIWebView is the first responder is as follows:
+(BOOL)isFirstResponder:(UIView *)v{
for (UIView *vs in v.subviews) {
if ([vs isFirstResponder] || [self isFirstResponder:vs]) {
return YES;
}
}
return NO;
}
-(BOOL)isFirstResponder{
return [[self class] isFirstResponder:self];
}
This way, the returned value will be YES even before/after the keyboard animation finishes (showing or hiding).
I met the same problem recently, but solved it using pure JavaScript. Actually it doesn't need any Objective-C First Responder related methods. I just used the JavaScript to change the UIWebView's content - the targeting HTML element's contentEditable attribute value according to the requirement.
For example, using the following code to hide the Keyboard that called by the UIWebView's editable content:
[webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('target').setAttribute('contentEditable','false')"];
Hope this is helpful. :)
Here is how I overwrite these methods in a UIWebView subclass (content is the id of the editable element):
[_webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('content').focus()"];
But Focus go to 1st point not last point
Call the following lines of code when you want to hide the keyboard.
//wView is your UIWebView
NSString *webText = [wView stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"];
[wView loadHTMLString:webText baseURL:nil];
[webView loadHTMLString:[NSString stringWithFormat:#"%#", htmlString] baseURL:nil];
This works in iOS > 4
Related
I'm using a UIWebViewthat loads HTML from a database string using webView.loadHTMLString(self.htmlContent, baseURL: nil)
The htmlContent contains the following:
<ul class="anchorNavigation">
<li>
1. Inline Test Link
</li>
<li>
2. Inline Test Link
</li>
...
</ul>
... and later in the HTML:
...
...
However, whenever I click the inline link in the webView nothing happens.
What I've tried so far:
Changing the anchor tag to 'real' valid W3C HTML. E.g. <a id='parsys_47728'>Test</a>
Saving the HTML to a file in the temp directory and loading it using loadRequest(). E.g. let path = tempDirectory.URLByAppendingPathComponent("content.html") and webView.loadRequest(NSURLRequest(URL: path))
Intercepting the loadRequest method by implementing the func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool delegate. The request.URL says something strange like: "applewebdata://1D9D74C2-BBB4-422F-97A7-554BCCD0055A#parsys_47728"
I don't have any idea anymore how to achieve this. I know from previous projects that local HTML files in the bundle work with inline links. I just cannot figure out why this doesn't work.
Help much appreciated! Thank you!
If there's a fragment (e.g., #anchorName), then use JavaScript to scroll. Otherwise, assume it's a link without a fragment and use openURL.
// UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked ) {
// NSLog(#"request.URL %#",request.URL); // e.g., file:///.../myApp.app/#anchorName
NSString *anchorName = request.URL.fragment; // e,g, "anchorName"
if ( anchorName ) {
[webView stringByEvaluatingJavaScriptFromString:[NSString swf:#"window.location.hash='%#';",anchorName]];
return NO;
} else { // assume http://
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
}
return YES;
}
I'm still looking for a way to have the scroll position change smoothly (animated) rather than jumping.
I have a UIWebview contains a html "select" tag, which is shown as a on the screen.
When I click the dropdown, the UIWebview brings up a UIWebSelectSinglePicker View automatically, which is shown as .
I want to change the picker view background color and text color. How can I achieve this goal?
I tried to listen on UIKeyboardWillShowNotification event, but at that moment, this view has not been created.
Thanks in advance for any helps.
I managed to resolve the issue myself.
If someone also want to change the UIPickView on the fly, please take a look:
First, add a listener on UIKeyboardWillShowNotification event.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_pickerViewWillBeShown:) name:UIKeyboardWillShowNotification object:nil];
Second, when notification fired, call change background color method after delay. <-- This is very important, if call method without delay, the pickview does not exist at that moment.
- (void)_pickerViewWillBeShown:(NSNotification*)aNotification {
[self performSelector:#selector(_resetPickerViewBackgroundAfterDelay) withObject:nil afterDelay:0];
}
Third, go through the UIApplication windows and find out pickerView. And you can change what ever you want for pickerView.
-(void)_resetPickerViewBackgroundAfterDelay
{
UIPickerView *pickerView = nil;
for (UIWindow *uiWindow in [[UIApplication sharedApplication] windows]) {
for (UIView *uiView in [uiWindow subviews]) {
pickerView = [self _findPickerView:uiView];
}
}
if (pickerView){
[pickerView setBackgroundColor:UIColorFromRGB(0x00FF00)];
}
}
(UIPickerView *) _findPickerView:(UIView *)uiView {
if ([uiView isKindOfClass:[UIPickerView class]] ){
return (UIPickerView*) uiView;
}
if ([uiView subviews].count > 0) {
for (UIView *subview in [uiView subviews]){
UIPickerView* view = [self _findPickerView:subview];
if (view)
return view;
}
}
return nil;
}
Hope it will help.
I believe I've come up with an alternate solution to this problem. There are certain circumstances with the other solution proposed where the label colours appear incorrect (using the system default instead of the overridden colour). This happens while scrolling the list of items.
In order to prevent this from happening, we can make use of method swizzling to fix the label colours at their source (rather than patching them after they're already created).
The UIWebSelectSinglePicker is shown (as you've stated) which implements the UIPickerViewDelegate protocol. This protocol takes care of providing the NSAttributedString instances which are shown in the picker view via the - (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component method. By swizzling the implementation with our own, we can override what the labels look like.
To do this, I defined a category on UIPickerView:
#implementation UIPickerView (LabelColourOverride)
- (NSAttributedString *)overridePickerView:(UIPickerView *)pickerView
attributedTitleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
// Get the original title
NSMutableAttributedString* title =
(NSMutableAttributedString*)[self overridePickerView:pickerView
attributedTitleForRow:row
forComponent:component];
// Modify any attributes you like. The following changes the text colour.
[title setAttributes:#{NSForegroundColorAttributeName : [UIColor redColor]}
range:NSMakeRange(0, title.length)];
// You can also conveniently change the background of the picker as well.
// Multiple calls to set backgroundColor doesn't seem to slow the use of
// the picker, but you could just as easily do a check before setting the
// colour to see if it's needed.
pickerView.backgroundColor = [UIColor yellowColor];
return title;
}
#end
Then using method swizzling (see this answer for reference) we swap the implementations:
[Swizzle swizzleClass:NSClassFromString(#"UIWebSelectSinglePicker")
method:#selector(pickerView:attributedTitleForRow:forComponent:)
forClass:[UIPickerView class]
method:#selector(overridePickerView:attributedTitleForRow:forComponent:)];
This is the Swizzle implementation I developed based off the link above.
#implementation Swizzle
+ (void)swizzleClass:(Class)originalClass
method:(SEL)originalSelector
forClass:(Class)overrideClass
method:(SEL)overrideSelector
{
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method overrideMethod = class_getInstanceMethod(overrideClass, overrideSelector);
if (class_addMethod(originalClass,
originalSelector,
method_getImplementation(overrideMethod),
method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(originalClass,
overrideSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
#end
The result of this is that when a label is requested, our override function is called, which calls the original function, which conveniently happens to return us a mutable NSAttributedString that we can modify in anyway we want. We could completely replace the return value if we wanted to and just keep the text. Find the list of attributes you can change here.
This solution allows you to globally change all the Picker views in the app with a single call removing the need to register notifications for every view controller where this code is needed (or defining a base class to do the same).
So another issue I'm having with UICollectionView.
This time I'm trying to show embed videos (YouTube) on a UICollectionViewCell.
The Embed method doesn't work, it gives me blank cells.
If I use the path directly, works, but it will show the whole page on Youtube instead of the video only.
Any idea what could it be?
Here's the code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
L4VideoCell *videocell = [collectionView dequeueReusableCellWithReuseIdentifier:#"videoCell" forIndexPath:indexPath];
videocell.backgroundColor = [UIColor whiteColor];
NSString *webpath = #"<iframe width=\"420\" height=\"315\" src=\"//www.youtube.com/embed/rmx55fxRK5A?rel=0\" frameborder=\"0\" allowfullscreen></iframe>";
[videocell.webAudioView loadHTMLString:webpath baseURL:nil];
return videocell;
}
Ok, found the issue.
I'm not sure if this reason is valid but it worked for me.
Before, I was passing the baseURL:nil. I added my own base URL and it worked.
[webview loadHTMLString:"Embed URL from YouTube" baseURL:[NSURL URLWithString:#"http://www.myurl.com"]];
Hope it helps.
I'm trying to open one link in safari when i click the UIWebview (its like ad display).
Following code am using but what happend is when i click the webview its opening inside UIWebview for some links (not all).
- (BOOL)webView:(UIWebView *)webView1 shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if (webView1==webview) {
if (UIWebViewNavigationTypeLinkClicked == navigationType) {
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
return YES;
}
}
what happens here is if any text link in that UIWebView then its opening correctly ,But if a UIWebview with images then its opening in same UIWebview instead of a new browser.
MY CURRENT CODE
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.example.com/files/ad.htm"]]];
[_webView setBackgroundColor:[UIColor clearColor]];
[_webView setOpaque:NO];
_webView.scrollView.bounces = NO;
}
-(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {
if ( inType == UIWebViewNavigationTypeLinkClicked ) {
[[UIApplication sharedApplication] openURL:[inRequest URL]];
return NO;
}
return YES;
}
Seems like those images has been linked using anchor tags...
Try this...
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
static NSString *reguler_exp = #"^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])[.])+([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$";
//Regular expression to detect those certain tags..You can play with this regular expression based on your requirement..
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", reguler_exp];
//predicate the matched tags.
if ([resultPredicate evaluateWithObject:request.URL.host]) {
[[UIApplication sharedApplication] openURL:request.URL];
return NO;
} else {
return YES;
}
}
EDIT: Don't allow this delegate method to get called for the first time.Add some logic that it won't get called for the 1st time loading the webview.
(SOP's new requirement,see below comment)
How to to detect the touch on uiwebview,
1)Add the tapgesture to your webview.(before that add UIGestureRecognizerDelegate in your viewcontroller.h)
example:
UITapGestureRecognizer* singleTap=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleSingleTap:)];
singleTap.numberOfTouchesRequired=1;
singleTap.delegate=self;
[webview addGestureRecognizer:singleTap];
2)Then add this delegate method,
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
//return YES,if you want to control the action natively;
//return NO,in case your javascript does the rest.
}
Note: In case you want to check whether the touch is native or htmml(javascript) check the nice little tutorial here,about handling the javascript events etc....Hope this helps you..
I have an NSButton that, when clicked, opens an NSPopover, my only problem is that when the button is clicked again, the NSPopover opens again on top of the other one, the pop over is opened using:
- (IBAction)openSettingsPopover:(id)sender {
NSViewController *controller = [[NSViewController alloc] initWithNibName:#"Settings" bundle:nil];
NSPopover *popover = [[NSPopover alloc] init];
[popover setContentSize:NSMakeSize(288.0f, 170.0f)];
[popover setContentViewController:controller];
[popover setAnimates:YES];
[popover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxXEdge];
}
How would I go about dismissing the popover rather than opening another one, same for if the user clicks outside the view? Thanks in advance.
Keep a reference to the NSPopover instance, check if it's not-nil before opening a new one
- (IBAction)openSettingsPopover:(id)sender {
if (self.settingsPopover) {
// Close it when clicked again, or simply return
[self.settingsPopover close];
}
self.settingsPopover = [[NSPopover alloc] init];
...
}
An easier way is to change it's behavior.
Just set the behavior property to NSPopOverBehaviourTransient and when the user interacts with another UI element not in the popover it will be dismissed.
[popover setBehaviour:NSPopOverBehaviourTransient];