I have specified dataDetectorTypes on a UITextView so that URLs open in Safari when touched.
Is it possible to intercept this behaviour so I load the URL in a UIWebView instead? Or would I have write my own URL detector code to re-route this?
You would have to do the URL detection yourself and manually load the UIWebView when the URL is tapped.
Everything needs to be custom-done because Apple sends all http:// and https:// URL openings to Safari.
The answer above that works best is the replacement of method implementation for [UIApplication openURL:]
Another way to achieve that, without using runtime.h is to subclass UIApplication. Then, override the openURL: selector. With this approach, you can call [super openURL:] from your subclass for URLs you want the default handling for. It also seems a little cleaner to me since you don't need to mess with the internal method implementations of the classes.
If you choose this approach, though, there are 2 other important steps. In the main.m file you need to change the 3rd argument to the UIApplicationMain function call so that it matches the name of your subclass:
int retVal = UIApplicationMain(argc, argv, #"MyApplicationSubclass", nil);
Also, you should probably change the class of the File's Owner in your MainWindow.xib file from UIApplication to your subclass.
I did everyone a favor and answered your question with a blog post and demo app.
http://52apps.net/post/879106231/method-swizzling-uitextview-and-safari
http://github.com/marksands/UITextViewLinkOptions
To expand on tt.Kilew's post, you create the category, but call your method something else such as customOpenURL. When you want to go back to Safari you do something called Method Swizzling. It looks like this:
#import <objc/runtime.h>
..
Method customOpenUrl = class_getInstanceMethod([UIApplication class], #selector(customOpenURL:));
Method openUrl = class_getInstanceMethod([UIApplication class], #selector(openURL:));
method_exchangeImplementations(customOpenUrl, openUrl);
Just call this method to swap the openURL implementation with your customOpenURL implementation when you do and don't want to use Safari.
Check out the demo app for more detail. Hope this helps! :)
Edit
If you don't want to risk your app getting rejected, you might want to check out a custom UITextView I developed to better suit the situation: https://github.com/marksands/MSTextView
Another Answer :) That works fine for me is to re-implement UIApplication openURL:(NSURL *) url
#interface UIApplication (Private)
- (BOOL)openURL:(NSURL*)url;
#end
#implementation UIApplication (Private)
- (BOOL)openURL:(NSURL*)url {
// all viewcontrollers should change currentViewController to self
if ([MyWatcher currentViewController]) {
// Do anything you want
[[MyWatcher handleURL:url withController:[MyWatcher currentViewController]];
return YES;
}
return NO;
}
#end
... Some view controller
- (void)viewDidLoad {
[super viewDidLoad];
[MyWatcher setCurrentController:self];
}
Swift version:
Your standard UITextView setup should look something like this, don't forget the delegate and dataDetectorTypes.
var textView = UITextView(x: 10, y: 10, width: CardWidth - 20, height: placeholderHeight) //This is my custom initializer
textView.text = "dsfadsaf www.google.com"
textView.selectable = true
textView.dataDetectorTypes = UIDataDetectorTypes.Link
textView.delegate = self
addSubview(textView)
After your class ends add this piece:
Note that you need https://github.com/TransitApp/SVWebViewController this library, which is the best one out there as far as I know.
class myVC: UIViewController {
//viewdidload and other stuff here
}
extension MainCard: UITextViewDelegate {
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
//Do your stuff over here
var webViewController = SVModalWebViewController(URL: URL)
view.presentViewController(webViewController, animated: true, completion: nil)
return false
}
}
You can try implementing application:handleOpenURL: in your Application Delegate.
This method should get called whenever a url gets opened. Here you should be able to make the URL open in your webview.
Related
I have a UIWebView with a contentEditable div in order to implement some kind of rich text editor. I need to trimm the copy & cut options in the UIMenuController that appears in the web view once the user selects any piece of text.
There seems to be a lot of solutions around the web, but for some reason, non of them applies for my scenario.
I've subclassed the UIWebView and implemented the canPerformAction:(SEL)action withSender: to remove the copy and cut, but once the user chooses "Select" or "Select All", a new menu appears, and apparently, the web view does not intercept this action and the canPerform method is not being called.
Is there a way to trimm actions for this cases?
I will adapt another answer of mine for your case.
The canPerformAction: is actually called on the internal UIWebDocumentView instead of the UIWebView, which you cannot normally subclass. With some runtime magic, it's possible.
We create a class which has one method:
#interface _SwizzleHelper : UIView #end
#implementation _SwizzleHelper
-(BOOL)canPerformAction:(SEL)action
{
//Your logic here
return NO;
}
#end
Once you have a web view which you want to control the actions of, you iterate its scroll view's subviews and take the UIWebDocumentView class. We then dynamically make the superclass of the class we created above to be the subview's class (UIWebDocumentView - but we cannot say that upfront because this is private API), and replace the subview's class to our class.
#import "objc/runtime.h"
-(void)__subclassDocumentView
{
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasPrefix:#"UIWeb"])
subview = view;
}
if(subview == nil) return; //Should not stop here
NSString* name = [NSString stringWithFormat:#"%#_SwizzleHelper", subview.class.superclass];
Class newClass = NSClassFromString(name);
if(newClass == nil)
{
newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
if(!newClass) return;
Method method = class_getInstanceMethod([_SwizzleHelper class], #selector(canPerformAction:));
class_addMethod(newClass, #selector(canPerformAction:), method_getImplementation(method), method_getTypeEncoding(method));
objc_registerClassPair(newClass);
}
object_setClass(subview, newClass);
}
I need to use a UIPopOverController for my iPhone app ,i searched stackoverflow someone said UIPopoverController does not run on iphone iphone device WHY?.when i run on iphone device
i got this error reason: '-[UIPopoverController initWithContentViewController:]
called when not running under UIUserInterfaceIdiomPad.'
-(void)btnSetRemainderTapped:(UIButton *)button
{
setReminderView =[[SetRemainderView alloc]initWithNibName:#"SetRemainderView" bundle:[NSBundle mainBundle]];
setReminderView.contentSizeForViewInPopover = CGSizeMake(setReminderView.view.frame.size.width, setReminderView.view.frame.size.height);
setReminderView.delegate = self;
popOverController = [[UIPopoverController alloc]
initWithContentViewController:setReminderView] ;
CGRect rect = CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/2, 1, 1);
[popOverController presentPopoverFromRect:rect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
can any one help me?
You CAN use popoverController in iPhone apps.
1. Create a category
// UIPopoverController+iPhone.h file
#interface UIPopoverController (iPhone)
+ (BOOL)_popoversDisabled;
#end
// UIPopoverController+iPhone.m file
#implementation UIPopoverController (iPhone)
+ (BOOL)_popoversDisabled {
return NO;
}
#end
2. Import it to your class and use popover in iPhone as usual.
But remember that this is private method and Apple can reject your app. But I know people who use this normally and Apple published their apps.
Edit: As stated by Soberman, since iOS 8 it is possible to present popovers on iPhone using public APIs, so this answer is probably not relevant anymore.
As stated in Apple's documentation on UIPopoverController:
Popover controllers are for use exclusively on iPad devices.
So there is no way to use this class in iPhone application unfortunately. But there are a couple of custom third-party implementations of the functionality provided by UIPopoverController which add iPhone support and more. See https://github.com/50pixels/FPPopover for example.
Edit: There also is another highly customizable popover implementation for both iPhone/iPad worth checking out: https://github.com/nicolaschengdev/WYPopoverController.
Since iOS8 we are now able to create popovers, that will be the same on iPhone, as on iPad, which would be especially awesome for those who make universal apps, thus no need to make separate views or code.
You can get the class as well as demo project here: https://github.com/soberman/ARSPopover
All you need to do is subclass UIViewController, conform to the UIPopoverPresentationControllerDelegate protocol and set desired modalPresentationStyle along with the delegate value:
// This is your CustomPopoverController.m
#interface CustomPopoverController () <UIPopoverPresentationControllerDelegate>
#end
#implementation CustomPopoverController.m
- (instancetype)init {
if (self = [super init]) {
self.modalPresentationStyle = UIModalPresentationPopover;
self.popoverPresentationController.delegate = self;
}
return self;
}
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone; //You have to specify this particular value in order to make it work on iPhone.
}
Afterwards, instantiate your newly created subclass in the method from which you want to show it and assign two more values to sourceView and sourceRect. It looks like this:
CustomPopoverController *popoverController = [[CustomPopoverController alloc] init];
popoverController.popoverPresentationController.sourceView = sourceView; //The view containing the anchor rectangle for the popover.
popoverController.popoverPresentationController.sourceRect = CGRectMake(384, 40, 0, 0); //The rectangle in the specified view in which to anchor the popover.
[self presentViewController:popoverController animated:YES completion:nil];
And there you have it, nice, neat blurred popover.
So #Sobermans answer didn't really solve the issue from start to finish for me so I want to detail how I got it done using the docs. That being said I do like the idea of using your own presentation controller subclass to manage all of the customisation you want to exhibit.
1. Create your controller to present
The first step is instantiating the controller you want to present:
let vc: UIViewController = ...
vc.modalPresentationStyle = .Popover
vc.preferredContentSize = CGSize(width: CGRectGetWidth(view.bounds)/2, height: 100)
Now we have a controller with the popover presentation style and an arbitrary content size.
2. Implement adaptivePresentationStyleForPresentationController
By default UIPopoverPresentationController will present on full screen on iPhone so to prevent this behaviour you need to force the adaptive presentation style to none.
First we set the delegate of the popover presentation controller
vc.popoverPresentationController.delegate = self;
Then we implement UIPopoverPresentationControllerDelegate
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None;
}
3. Present and configure popup
First we need to call presentViewController and only after that can we configure the popover:
presentViewController(vc, animated:true, completion:nil)
if let popover = vc.popoverPresentationController {
popover.permittedArrowDirections = .Right | .Left
popover.sourceView = button
popover.sourceRect = button.bounds
}
Use a custom popover controller, such as:
https://github.com/sammcewan/WYPopoverController
(this seems to be the best supported one that I have found).
I ended up creating my custom tooltip/popover class.
Can be initalised with any content view and dynamically adjusts it's frame.
Hope it helps.
https://github.com/akeara/AKETooltip
If you want to do it in Swift, I believe the code is the following:
extension UIPopoverController {
class var _popoversDisabled : Bool {
get { return false }
}
}
Edit: It is working in Xcode 6 beta 4 on iPhone with iOs7.1
This is a really interesting (and depressing) thread to read. I can't believe Apple prevents popup dialogs on iPhones, with absolutely no justification.
And, it's true, on iOS 8, if you try to work around this limitation, it'll make your popups appear as a full-screen modal dialog.
The following excellent webpage describes "How Apple Cheats" to let its own iBooks and iTunes apps break its own rules, and allow popups - but just from within their own iPhone apps.
HowAppleCheats
Have a read (warning: it'll make you hate Apple & XCode even more..)
Want to get around the "UIPopoverController called when not running under UIUserInterfaceIdiomPad" error on iOS 8 ?
Simple.
Just go into your .plist file, and change the Bundle ID to "com.apple.itunesu" to make XCode think that your app is actually iTunes.
Then your popup will work fine.
(Sigh.)
The alternative way of doing this is to directly add your UIViewController to your screen.
In this example, I wanted a "helper screen" to appear on top of my iPhone screen. It's a UIViewController, it is stored in it's own .xib file, and it has a few lines to add a pretty border:
- (void)viewDidLoad {
[super viewDidLoad];
// Give our popup a pretty curved border
self.view.layer.borderColor = [[UIColor blueColor] CGColor];
self.view.layer.borderWidth = 1.0;
self.view.layer.cornerRadius = 8;
}
To display it, I simply create an instance of this UIViewController, add it to my screen, then center it:
-(void)showHelperScreen
{
if (self.helperScreen == nil)
{
// Add the popup UIViewController to our screen
self.helperScreen = [[HelperViewController alloc] init];
[self.view addSubview:self.helperScreen.view];
}
// Center the popup in the middle of the screen
CGSize screenSize = [[UIScreen mainScreen] applicationFrame].size;
self.helperScreen.view.center = CGPointMake(screenSize.width/2, screenSize.height/2);
}
Of course, I also needed to add some code to make the popup disappear when the user taps outside of it, but this does at least show that you can (safely) display popups on an iPhone, even if your app isn't specifically called iTunes or iBook.
Voila.
Hope this helps, and if anyone needs me, I'll be back in my safe, happy place (Visual Studio, in other words).
I have a URL in my textview and when we click on this url it opens this url in default safari. and I want to detect this event. I also tried this
but its not working for me. can any one suggest me how i do this. Provide me a sample for this.
My application deligate is UIResponder type.
the other answer works as expected.
create New File, select Objective-C Class
Class: MyApplication
Subclass of: UIApplication
paste this code in the .m file:
- (BOOL)openURL:(NSURL *)url {
if ([self handleOpenURL:url])
return YES;
else
return [super openURL:url];
}
- (BOOL)handleOpenURL:(NSURL*)url {
NSLog(#"my url handler");
return YES;
}
next open your main.m and change the third parameter
return UIApplicationMain(argc, argv, nil, NSStringFromClass([SampleAppDelegate class]));
to your UIApplication-subclass name
return UIApplicationMain(argc, argv, #"MyApplication", NSStringFromClass([SampleAppDelegate class]));
Take a look at this GitHub project: MSTextView
Assuming you just need to display a static label with links, we have a fairly powerful attributed label in Nimbus that you may wish to check out:
http://docs.nimbuskit.info/group___nimbus_attributed_label.html
The label uses CoreText and NSAttributedString, so it is built on core Apple technologies. It acts in every way like a UILabel. Here's an example of implementing the NIAttributedLabel delegate:
https://github.com/jverkoey/nimbus/blob/master/examples/attributedlabel/BasicAttributedLabel/BasicAttributedLabel/src/MashupViewController.m#L92
More info:
http://nimbuskit.info/
I am pretty new with Three20. I have followed ray wenderlich's nice introduction to three20 and the examples within the three20 framework. When I click on a thumbnail in a thumbnail view (subclass of TTThumbsViewController) to launch a Details view, a standard Details image view (deployed by TTPhotoViewController or its super class). I would like to use my own implementation of a Details View instead of the default. I put the following code when I initiated the subclass of TTThumbsViewController and TTThumbsViewControllerDelegate method:
- (id)initWithDelegate:(id<TTThumbsViewControllerDelegate>)delegate {
[super initWithDelegate:delegate];
return self;
}
- (void)thumbsViewController: (TTThumbsViewController*)controller
didSelectPhoto: (id<TTPhoto>)photo {
[navigationController.pushViewController:photoDetailViewController
animated:Yes];
}
But the default TTPhotoViewController view still prevail. When I put a NSLog in the delegate method. I coud see the method was called. I think there is another delegate someone already set in TTThumViewController? Can someone recommend a way to display my detail photo view? Is there another thumbs view controller I can use? Any suggestion will be greatly appreciated.
I'm really new to all of this (coding, etc.) but I'll share what I've found. By looking up the definition of ttthumbsviewcontroller, I was able to find the following method(wrong term?):-
- (void)thumbsTableViewCell:(TTThumbsTableViewCell*)cell didSelectPhoto:(id<TTPhoto>)photo {
[_delegate thumbsViewController:self didSelectPhoto:photo];
BOOL shouldNavigate = YES;
if ([_delegate respondsToSelector:#selector(thumbsViewController:shouldNavigateToPhoto:)]) {
shouldNavigate = [_delegate thumbsViewController:self shouldNavigateToPhoto:photo];
}
if (shouldNavigate) {
NSString* URL = [self URLForPhoto:photo];
if (URL) {
TTOpenURLFromView(URL, self.view);
} else {
TTPhotoViewController* controller = [self createPhotoViewController];
controller.centerPhoto = photo;
[self.navigationController pushViewController:controller animated:YES];
}
}
}
In the else statement, I've found this calls the creation of the photoviewcontroller. By recalling this method (?) in the actual body of my own code and changing the body in the else statement I was able to add a custom detail view. Further down the definition of the ttthumbsnailviewcontroller, you can find that the creatPhotoViewController calls for an initiation of the PhotoViewController so calling that method(?) in the body of the code and initializing another view also works.
If someone can explain whether or not this is a good method of doing this (I have a feeling that is not), it would be appreciated. Also why does putting the method in the body of the code override the call there.
I'm really not good with these protocols, especially because this is really my first time using them. I have two UIWebViews in the same view: webView and webView2. How do I change this line of code to work for webView2?
-(void)webViewDidStartLoad:(UIWebView *)webView {
I tried changing "webView" to "webView2", but Xcode said that I had the same line twice, so obviously this won't work. What should I do? Thanks for your help!
Btw, I'm thinking I have to add an IF statement within here, but what should it be?
The delegate method passes in a parameter defining which web view it’s coming from for exactly this reason.
- (void)webViewDidStartLoad:(UIWebView *)theWebView
{
if(theWebView == webView)
{
// do something
} else if(theWebView == webView2)
{
// do something else
}
}
Well the same method will be called, but the ref of the webview is passed in,
So you can keep a reference to both your webviews in your delegate and say something like if(webview1==webview) which will evaluate to yes only if the webview ref passed in the delegate method is webview1 so you can figure out which webview is calling the delegate using the if statement
I think you want:
- (void)webViewDidStartLoad:(UIWebView *)webView; {
if(webView == webView1){
// use the first webview here.
}
if(webView == webView2){
// use the second webview here.
}
}
Hope that Helps!