I'm totally baffled. I am trying to let the user send an email via MFMailComposeViewController. However, when I set up my MFMailComposeViewController with a HTML body, the MFMailComposeViewController displays the email correctly, but on sending the mail my application crashes with a log like this:
2011-12-03 15:04:08.708 Kinopilot[58387:17503] *** Terminating app due to uncaught
exception 'DOMException', reason: '*** INVALID_ACCESS_ERR: DOMException 15'
*** First throw call stack:
(0x2994052 0x210fd0a 0x2993f11 0x391affb 0x3886795 0x64a9b4 0x6487b6
0x648b84 0x648bb4 0x648bb4 0x6424b9 0x69078b 0x658fad 0x658eeb 0x658e6f
0x656d37 0x656bb2 0x65737c 0x65759e 0x658273 0x658758 0x6588e6 0x2995ec9
0x10965c2 0x12d1d54 0x2995ec9 0x10965c2 0x109655a 0x113bb76 0x113c03f
0x113b2fe 0x10bba30 0x10bbc56 0x10a2384 0x1095aa9 0x2270fa9 0x29681c5
0x28cd022 0x28cb90a 0x28cadb4 0x28caccb 0x226f879 0x226f93e 0x1093a9b 0x6680d 0x663d5)
I am pretty confident that the MFMailComposeViewController is wired correctly, because all is fine when I set the body as plain text. Besides, my code works fine w/HTML and plain text on iOS 4.3.
This is the relevant code:
#interface MailComposeDelegate: NSObject<MFMailComposeViewControllerDelegate>
#end
#implementation MailComposeDelegate
+(MailComposeDelegate*) sharedMailComposeDelegate
{
static MailComposeDelegate* mcd = nil;
if(!mcd) {
mcd = [[MailComposeDelegate alloc]init];
}
return mcd;
}
-(void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error
{
[app.window.rootViewController dismissModalViewControllerAnimated:NO];
}
...
#implementation M3AppDelegate(Email)
-(void)composeEmailWithSubject: (NSString*)subject
andBody: (NSString*)body
{
// -- compose HTML message.
MFMailComposeViewController* mc;
if([MFMailComposeViewController canSendMail])
mc = [[MFMailComposeViewController alloc] init];
if(!mc) return;
mc.mailComposeDelegate = [MailComposeDelegate sharedMailComposeDelegate];
[mc setSubject: subject];
[mc setMessageBody: body isHTML: YES];
// -- show message composer.
[app.window.rootViewController presentModalViewController:mc animated:YES];
}
#end
Ha, answering my own question here: some iOS5-component wouldn't process some of my markup, namely a
<div style='font-size: 85%'>
div. Changing the relative size into an absolute pixel height worked fine, though.
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 am trying to create an iOS app simply to extract the section of a web page.
I have the code working to connect to the URL and store the HTML in an NSString
I have tried this, but I am just getting null strings for my result
NSScanner* newScanner = [NSScanner scannerWithString:htmlData];
// Create a new scanner and give it the html data to parse.
while (![newScanner isAtEnd])
{
[newScanner scanUpToString:#"<body>" intoString:NULL];
// Scam until <body> tag is found
[newScanner scanUpToString:#"</body>" intoString:&bodyText];
// Everything up to the end tag will get placed into the memory address of the result string
}
I have tried an alternative way...
NSScanner* newScanner = [NSScanner scannerWithString:htmlData];
// Create a new scanner and give it the html data to parse.
while (![newScanner isAtEnd])
{
[newScanner scanUpToString:#"<body" intoString:NULL];
// Scam until <body> tag is found
[newScanner scanUpToString:#">" intoString:NULL];
// Go to end of opening <body> tag
[newScanner scanUpToString:#"</body>" intoString:&bodyText];
// Everything up to the end tag will get placed into the memory address of the result string
}
This second way returns a string which starts with >< script... etc
If Im honest I don't have a good URL to test this with and I think It may be easier with some help on removing the tags within the body too (like <p></p>)
Any help would be very much appriciated
I don't know why your first method didn't work. I assume you defined bodyText before that snippet. This code worked fine for me,
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlData = #"This is some stuff before <body> this is the body </body> with some more stuff";
NSScanner* newScanner = [NSScanner scannerWithString:htmlData];
NSString *bodyText;
while (![newScanner isAtEnd]) {
[newScanner scanUpToString:#"<body>" intoString:NULL];
[newScanner scanString:#"<body>" intoString:NULL];
[newScanner scanUpToString:#"</body>" intoString:&bodyText];
}
NSLog(#"%#",bodyText); // 2015-01-28 15:58:00.360 ScanningOfHTMLProblem[1373:661934] this is the body
}
Notice that I added a call to scanString:intoString: to get past the first "<body>".
I just submitted my first app to the app store (yay it was just approved!). I now want to update it to work with (look nicer on) the larger iPhone 5 screen. I don't intend to change anything other than to change the layout a bit for the larger screen.
NOTE: I don't want to have my current xib stretched.
Is it possible to create two xib files (ie: copy my current xib file for the main screen) and hook them both into the view controller and have it so that when the app launches, the app detects if there is an iPhone 5 screen or an earlier screen. Then, depending on which device it is, show the user a different screen.
I intend for underlying app to remain the same. All I want is to present a slightly different (taller) screen for iPhone 5 users with a few buttons/items moved around for the new layout. I otherwise won't be adding or removing anything from the interface.
This SO question/answer shows how to switch between an iPhone or iPad view. So to does this one. Both are helpful but I don't know how to modify this for the circumstance where the user is using an iPhone 5 with a larger screen or an iPhone 4S and below. Also, they assume two view controllers. I only want ONE view controller since absolutely NOTHING in the view controller logic changes - only the placement of the objects on the screen change and that is all done in the XIB.
I should think the answer should be that the view controller iteslf assesses what device it is running on then presents the appropriate xib? Yes? No?
If so, how would I go about this?
[Revised with Complete Answer on : Oct 7, 2012]
After significant research I found the answer, partly on this SO page (which shows how to detect which iPhone version your app is running on) and partly this SO page (showing how to have two xib's share the same 'File's Owner'. The final piece of the puzzle (loading separate xib's via the loadNibNamed: method) I found in chapter 10 of The Big Nerd Ranch's excellent iOS Programming text. Here's how:
Create a second xib (File, New..., File, select 'User Interface', select 'Empty' and save it. This creates the new xib. In the example below, my classic xib (for 3.5" iPhones) was named TipMainViewController.xib. I saved the new iPhone 5 xib with the name 'TipMainViewController-5.xib'
Make that new xib's 'File's Owner' the same ViewController as your existing xib. To do this, in the new xib file, select 'File's Owners'. Then in the 'Identity Inspector' select the existing View Controller as the Custom Class. In my case I selected 'TipMainViewController'.
Drag a new UIView onto the new xib's empty canvas. In the new UIView's attribute inspector set the 'Size' attribute to 'Retina 4 Full Screen'
Select all the contents in the existing 'Classic' 3.5" xib - eg: all your controls, buttons, selectors, labels etc. Copy them and paste them into the new iPhone 5 xib. Resize/move etc. them to optimize for the iPhone's 4" display.
Make all the connections to/from File's Owner as you did when you created your original xib.
Finally, in the 'viewDidLoad' method of your 'single' ViewController, insert the following logic (using your nib/xib names of course):
- (void)loadView
{
[super viewDidLoad];
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
CGSize result = [[UIScreen mainScreen] bounds].size;
if(result.height == 480)
{
// iPhone Classic
[[NSBundle mainBundle] loadNibNamed:#"TipMainViewController" owner:self options:nil];
}
if(result.height == 568)
{
// iPhone 5
[[NSBundle mainBundle] loadNibNamed:#"TipMainViewController-5" owner:self options:nil];
}
}
}
Here is a simple, working code sample for your view controller that shows how to load myXib-5.xib on the iPhone 5 and myXib.xib on iPhones/iPods predating the iPhone 5:
- (void)loadView
{
if([[UIScreen mainScreen] bounds].size.height == 568)
{
// iPhone 5
self.view = [[NSBundle mainBundle] loadNibNamed:#"myXib-5" owner:self options:nil][0];
}
else
{
self.view = [[NSBundle mainBundle] loadNibNamed:#"myXib" owner:self options:nil][0];
}
}
It assumes that you are only targeting the iPhone and not the iPad, to keep it simple.
The XIB's file owner's class property should also be set to the view controller that contains loadView.
Code in answer was helpful, but I needed something that worked better for universal apps (iphone/ipad).
In case someone else needs the same thing, here's something to get you started.
Say you built a universal app using the nib/xib naming standards for ios for view controllers that have xibs with the same name:
The two built-in defaults for autoloading xibs when providing no name is passed to initWithNibName:
ExampleViewController.xib [iphone default when nib named empty for Retina 3.5 Full Screen for classic layouts iphone 4/4s etc...]
ExampleViewController~ipad.xib [ipad/ipad mini default when nib named empty]
Now say you need custom xibs for the iphone 5/5s in IB using Retina 4 Full Screen option, i.e., you don't want the 3.5 xibs displaying for any 568h devices.
Here's the custom naming convention using a category approach:
ExampleViewController-568h.xib [iphone non default/custom naming convention when nib name empty for Retina 4 Full Screen (568h)]
Instead of overriding the built-in naming defaults, use a category to help set the right xib for the controller.
https://gist.github.com/scottvrosenthal/4945884
ExampleViewController.m
#import "UIViewController+AppCategories.h"
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
nibNameOrNil = [UIViewController nibNamedForDevice:#"ExampleViewController"];
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Do any additional customization
}
return self;
}
UIViewController+AppCategories.h
#import <UIKit/UIKit.h>
#interface UIViewController (AppCategories)
+ (NSString*)nibNamedForDevice:(NSString*)name;
#end
UIViewController+AppCategories.m
// ExampleViewController.xib [iphone default when nib named empty for Retina 3.5 Full Screen]
// ExampleViewController-568h.xib [iphone custom naming convention when nib named empty for Retina 4 Full Screen (568h)]
// ExampleViewController~ipad.xib [ipad/ipad mini default when nib named empty]
#import "UIViewController+AppCategories.h"
#implementation UIViewController (AppCategories)
+ (NSString*)nibNamedForDevice:(NSString*)name
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
if ([UIScreen mainScreen].bounds.size.height == 568)
{
//Check if there's a path extension or not
if (name.pathExtension.length) {
name = [name stringByReplacingOccurrencesOfString: [NSString stringWithFormat:#".%#", name.pathExtension] withString: [NSString stringWithFormat:#"-568h.%#", name.pathExtension ]
];
} else {
name = [name stringByAppendingString:#"-568h"];
}
// if 568h nib is found
NSString *nibExists = [[NSBundle mainBundle] pathForResource:name ofType:#"nib"];
if (nibExists) {
return name;
}
}
}
// just default to ios universal app naming convention for xibs
return Nil;
}
#end
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
When I use the following code to insert a view on top of a split view, I am getting orientation problems.
Here is the code I use,
[window addSubview:aSplitViewController.view];
[window insertSubview:aViewController.view aboveSubview:aSplitViewController.view];
What happens here is that the view controller ( which contains labels and buttons) loads in landscape mode while its components load in portrait mode...
I feel that the window insertSubview is creating this problem because when I used [window addSubview:aViewController.view] the view is getting displayed properly in landscape mode with its components in landscape mode as well...
Here is the code which I feel is giving me the problem
In my App Delegate
- (void) makeSplitViewController {
NSMutableArray *controllers = [NSMutableArray arrayWithArray:tabBarController.viewControllers];
// First tabbbar item
// detail view
detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
UINavigationController *navDetailView = [[[UINavigationController alloc] initWithRootViewController:detailViewController] autorelease];
navDetailView.hidesBottomBarWhenPushed = YES;
// root view
rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
rootViewController.detailViewController = detailViewController;
rootViewController.navigationItem.title = #"List";
UINavigationController *navRootView = [[[UINavigationController alloc] initWithRootViewController:rootViewController] autorelease];
navRootView.hidesBottomBarWhenPushed = YES;
navRootView.navigationBar.barStyle = UIBarStyleBlackTranslucent;
splitViewController = [[UISplitViewController alloc] init];
splitViewController.tabBarItem.title = #"Face Sheet";
splitViewController.tabBarItem.image = [UIImage imageNamed:#"gear1.png"];
splitViewController.navigationItem.title = #"Face Sheet";
splitViewController.viewControllers = [NSArray arrayWithObjects:navRootView, navDetailView, nil];
splitViewController.delegate = detailViewController;
splitViewController.hidesBottomBarWhenPushed = YES;
[controllers addObject:splitViewController];
// Second tabbbar item
scoreViewController = [[ScoreCardViewController alloc] initWithNibName:#"TableViewController" bundle:nil];
scoreViewController.tabBarItem.title = #"Score Card";
scoreViewController.tabBarItem.image = [UIImage imageNamed:#"gear1.png"];
scoreViewController.navigationItem.title = #"Score Card";
[controllers addObject:scoreViewController];
tabBarController.viewControllers = controllers;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Create tabbar
tabBarController = [[UITabBarController alloc] init];
//tabBarController.delegate = self;
// Set window
[window addSubview:splashController.view];
[window insertSubview:tabBarController.view belowSubview:splashController.view];
[self.window makeKeyAndVisible];
application.statusBarOrientation = UIInterfaceOrientationLandscapeRight;
return YES;
}
and here is the code in my SplashScreenView
- (IBAction) proceedButtonClick:(id)sender
{
// Initialize loginpopview
PhysicianLoginViewController *loginViewController = [[PhysicianLoginViewController alloc] init];
popOverController = [[UIPopoverController alloc] initWithContentViewController:loginViewController];
popOverController.popoverContentSize = CGSizeMake(350, 200);
popOverController.delegate = self;
// Set a notification to dismiss it later
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loginViewControllerDone:) name:#"loginViewControllerDone" object:popOverController.contentViewController];
// Present popover
if ([popOverController isPopoverVisible])
{
[popOverController dismissPopoverAnimated:YES];
}
else
{
[popOverController presentPopoverFromRect:CGRectMake(485, 600, 100, 100) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}
}
// Dismiss popview controller and setup the tabbar
- (void)loginViewControllerDone:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Button in content view controller was tapped, dismiss popover...
[self.popOverController dismissPopoverAnimated:YES];
// remove subview
[self.view removeFromSuperview];
// set tabbar
i3EAppDelegate *appDelegate = (i3EAppDelegate *) [[UIApplication sharedApplication]delegate];
[appDelegate makeSplitViewController];
}
It would be great if someone could point out where I am going wrong. I have been stuck with this problem for quite a few days and I have tried everything that comes to my mind...
Your problem is that the rotation handling of UIWindow and UIViewController just isn't designed to work that way. Quoth the documentation:
In an iOS application, the window object does much of the work associated with changing the current orientation. However, it works in conjunction with the application’s view controllers to determine whether an orientation change should occur at all, and if so, what additional methods should be called to respond to the change. Specifically, it works with the view controller whose root view was most recently added to, or presented in, the window. In other words, the window object works only with the frontmost view controller whose view was displayed using one of the mechanisms described in “Presenting a View Controller’s View.”
This paragraph is somewhat vague and contradictory (is it the most recently added view controller, or the controller for the topmost view?), and in practice doesn't seem to necessarily match observations. The bottom line is that adding multiple views to a UIWindow will screw up the automatic rotation handling.
You should change your code to use presentModalViewController:animated: (maybe with modalPresentationStyle set to UIModalPresentationFormSheet) or a UIPopoverController instead of adding multiple subviews to the window.
Try:
[aViewController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];