I have a requirement to load a UIWebView wrapped inside a UINavigationController, and show it on a modal view. UINavigationController should show the navigation bar at the top with a 'back' (I did not find back button, so I used 'done' button). I am not supposed to use a nib, so I have to do it programmatically only. The code has to basically serve as a library which can be integrated with any application. It has to work for IPhone and IPad, both.
This is what I have done so far:
I created a WebViewController class -
#interface WebViewController : UIViewController
{
UIWebView *m_cWebView;
}
#property (nonatomic, retain) UIWebView *m_cWebView;
#end
- (void)loadView
{
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webFrame.size.height -= self.navigationController.navigationBar.frame.size.height;
UIWebView *pWebView = [[UIWebView alloc] initWithFrame:webFrame];
pWebView.autoresizesSubviews = YES;
pWebView.autoresizingMask=(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
self.view = pWebView;
pWebView.scalesPageToFit = YES;
self.m_cWebView = pWebView;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(back:)];
}
- (void)viewDidLoad
{
[super viewDidLoad];
if( m_cWebView != nil )
{
NSURL *url = [NSURL URLWithString:#"http://www.google.co.in"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[m_cWebView loadRequest:request];
}
}
- (IBAction)back:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
return nil;
}
- (id)init
{
return self;
}
In the main view controller
#property (retain, atomic) UINavigationController *navCon;
-(IBAction)buttonPressed:(id)sender
{
if( navCon == nil )
{
WebViewController* webViewController = [[WebViewController alloc] init];
navCon = [[UINavigationController alloc] initWithRootViewController:webViewController];
}
[self presentModalViewController:navCon animated:YES];
}
So far, it is working fine. Now my issues:
I am totally new to iOS world. Is the above code correct or are there issues?
Code is supposed to be compiled with Xcode 4.2 with ARC, so I presume that I don't need to worry about memory.
Is the logic to calculate the initial size of WebView correct (I am taking the size of main screen and deducting the height of navigation bar)?
How do I handle orientation change such that on changing the orientation, my Navigation View and WebView both adjust to the new orientation?
Thanks
Edit:
I tried implementing with the rotation, but I have not been able to get it to work. willRotateToInterfaceOrientation is not getting called on my WebViewController, even though I am returning YES from shouldAutorotateToInterfaceOrientation ( and this method is getting called ). When I tried working with an empty nib ( loading WebViewController with this empty nib ), then it works fine.
Adding the 5th requirement:
One more additional requirement is that when this modal dialog dismisses, the view which presented this, should be in correct orientation (i.e. while the modal dialog is up, the orientation events are also passed on to the view which presented this modal view).
Thanks
Some points from my side.
Regarding back button, in place of done:
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:nil action:nil] autorelease];
Height of navigation bar is always 44.0f
You can handle orientation change with these methods:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self adjustViewsForOrientation:toInterfaceOrientation]; // adjust your view frames here
}
I am not sure about your code and memory issues in that - so i could not comment over those.
Hope this information would be helpful to you.
I found the fix to the rotation problem.
Replace
- (id)init
{
return self;
}
with
- (id)init
{
if (self = [super init])
return self;
return nil;
}
and completely remove the following method:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
return nil;
}
Silly mistakes :)
Now it is rotating fine.
Can someone still comments on the original requirements?
Related
I am trying to show a UIImagePickerController from a button click. When I click the button, I get a SIGABRT at the line:
[self presentModalViewController:camera animated:YES];
from the code block:
camera = [[UIImagePickerController alloc]init];
[camera setSourceType:UIImagePickerControllerSourceTypeCamera];
[camera setDelegate:self.view];
camera.showsCameraControls = NO;
camera.navigationBarHidden = YES;
camera.wantsFullScreenLayout = YES;
camera.toolbarHidden = YES;
camera.cameraOverlayView = bottomArrow;
[self presentModalViewController:camera animated:YES];
where camera is the name of the UIImagePickerController defined as such:
UIImagePickerController *camera;
in the #interface.
My interface declaration is:
#interface cameraViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate> {
Can someone see what I'm doing wrong?
Besides the good point made by #Vikings, always check if your device has a camera before trying to use it:
if ([UIImagePickerController
isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[camera setSourceType:UIImagePickerControllerSourceTypeCamera];
} else {
[camera setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
}
Make sure you are using the both the Navigation Controller Delegate and the Image Picker Controller Delegate. The Image Picker is actually a Navigation Controller, which is why you have to implement its delegate.
#interface YourViewController : UITableViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
Also, set the delegate correctly, not to the view, but the View Controller.
camera.delegate = self;
The delegate needs to be set to the View Controller, and not the View Controller's View.
Check out the code below:
(1) You do not need to hide the navigation bar, because there is not one
(2) You do not need to hide the toolbar, because there is not one
(3) You do not need to specify wantsFullScreenLayout, because a Modal View Controller will always take up the full screen
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
picker.showsCameraControls = NO;
// Comment out the line below to make sure it is not causing a problem.
// This just expects a view, so if bottomArrow is a view you should be fine
picker.cameraOverlayView = bottomArrow;
[self presentModalViewController:picker animated:YES];
Also, I did not realize you were loading this code in viewDidLoad, this will crash, because the View Controller itself is not finished it's transition, so you cannot begin another transition. Instead use viewDidAppear for the same effect:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
// Place code here
}
Do not put your code in viewDidLoad create one IBAction and put your code inside that function.
and set delegate to your view controller. and in .h file make sure that your wrote
<UIImagePickerControllerDelegate>
[camera setDelegate : self];
I have three view controllers: A -> B -> C managed by a navigation controller. A is a transient view controller. It asks the server for something. If the server says everyhing is OK, then A pushes B onto the stack. B must hide the back button because I don't want users to manually go back to A.
// B view controller
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
self.title = #"B";
}
B then pushes C onto the stack when the user taps a table cell.
// B view controller
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
C *c = [[C alloc]
initWithStyle:UITableViewStyleGrouped
];
[self.navigationController
pushViewController:c
animated:YES
];
[c release];
}
.
// C view controller
- (void) viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton = NO;
self.title = #"C";
}
If all goes well, the flow should look like this:
------------- ------------- -------------
|_____A_____| |_____B ____| | <B|__ C___|
| | => | | => | |
| loading...| | cells | | detail |
| | | | | |
------------- ------------- -----------
For some reason, C does not show a back button to go back to B until I rotate the device. Once rotated, the back button appears in all orientations. The problem seems to stem from B hiding the back button and C trying to reveal it again, because If I don't let B hide it, I don't have this problem. So how do I get C to show the back button without forcing the user to rotate the device like a monkey?
Update
Broken on two different Verizon iPhone 4 both on iOS 4.2.10
Fine on AT&T iPhone 3GS on iOS 5.0
Fine on AT&T iPhone 4 on iOS 4.3
After some searching I found this solution for iPhone 4.2 (since you posted that it works on later versions) on some old forum post.
-(void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.navigationItem.hidesBackButton = NO;
}
Mayhaps this will help you out.
(Check this out: Back button don't appear in navigationController)
I think you need to put your code like this for C
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.navigationItem.hidesBackButton = NO;
}
I had this problem once and solved it by giving B's navigation item a title
// B view controller
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
self.navigationItem.title = #"What you want C's back button to say";
self.title = #"B";
}
If you don't want the title to appear in B you can set B's navigationItem's titleView to an empty view. About the titleView property:
If this property value is nil, the navigation item’s title is displayed in the center of the navigation bar when the receiver is the top item. If you set this property to a custom title, it is displayed instead of the title. This property is ignored if leftBarButtonItem is not nil.
Try adding this to your class C:
-(id) init
{
[super init];
self.navigationItem.hidesBackButton = NO;
}
Not sure if this will work for your back button situation but I know that when I use a custom back button I need to set the custom back button on the alloc'd item before I push it (not self like the post above). Hopefully it will work for your situation as well - worth a try.
In other words, try:
// B view controller
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
C *c = [[C alloc]
initWithStyle:UITableViewStyleGrouped];
// *** set on c's navigationItem (not self) before you push
[[c navigationItem] setHidesBackButton: NO];
[self.navigationController
pushViewController:c
animated:YES
];
[c release];
}
Try to put self.navigationItem.hidesBackButton = NO; into the init method or anywhere before the pushViewController call.
ViewDidLoad is called when you ask the controller for the view for the first time, that means, it is probably called from [self.navigationController pushViewController:c animated:YES]. But note that the navigation bar is not part of your view, it is created and handled by UINavigationController, so basically it can exist and be drawn even before viewDidLoad and viewDidAppear is called. If you update navigation bar there, it doesn't get actually repainted.
Edit 1: Revisited after reading the documentation for [UIViewController navigationItem]
You should avoid tying the creation of bar button items in your navigation item to the creation of your view controller’s view. The navigation item of a view controller may be retrieved independently of the view controller’s view. For example, when pushing two view controllers onto a navigation stack, the topmost view controller becomes visible, but the other view controller’s navigation item may be retrieved in order to present its back button. To ensure the navigation item is configured, you can override this property and add code to load the bar button items there or load the items in your view controller’s initialization code.
Edit 2: Revisited after reading the comment that my solution does not work. Working code (iOS 5, ARC):
//
// TestAppDelegate.m
// NavigationTest
//
// Created by Sulthan on 10/25/11.
// Copyright (c) 2011 StackOverflow. All rights reserved.
//
#import "TestAppDelegate.h"
#interface TestAppDelegate ()
#property (nonatomic, strong, readwrite) UINavigationController* navigationScreen;
#property (nonatomic, strong, readwrite) UIViewController* screen1;
#property (nonatomic, strong, readwrite) UIViewController* screen2;
#property (nonatomic, strong, readwrite) UIViewController* screen3;
#end
#implementation TestAppDelegate
#synthesize window = window_;
#synthesize navigationScreen = navigationScreen_;
#synthesize screen1 = screen1_;
#synthesize screen2 = screen2_;
#synthesize screen3 = screen3_;
- (UIViewController*)createTestScreenWithLabel:(NSString*)label {
CGRect bounds = [[UIScreen mainScreen] bounds];
UIViewController* screen = [[UIViewController alloc] init];
screen.view = [[UILabel alloc] initWithFrame:bounds];
screen.view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
((UILabel*) screen.view).textAlignment = UITextAlignmentCenter;
((UILabel*) screen.view).text = label;
return screen;
}
- (void)pushThirdScreen {
if (!self.screen3) {
self.screen3 = [self createTestScreenWithLabel:#"Screen 3"];
self.screen3.navigationItem.hidesBackButton = NO;
}
[self.navigationScreen pushViewController:self.screen3 animated:YES];
}
- (void)pushSecondScreen {
self.screen2 = [self createTestScreenWithLabel:#"Screen 2"];
self.screen2.navigationItem.hidesBackButton = YES;
UIBarButtonItem* button = [[UIBarButtonItem alloc] initWithTitle:#"Go"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(pushThirdScreen)];
self.screen2.navigationItem.rightBarButtonItem = button;
[self.navigationScreen pushViewController:self.screen2 animated:YES];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
CGRect bounds = [[UIScreen mainScreen] bounds];
self.screen1 = [self createTestScreenWithLabel:#"Screen 1"];
self.navigationScreen = [[UINavigationController alloc] initWithRootViewController:self.screen1];
self.window = [[UIWindow alloc] initWithFrame:bounds];
self.window.backgroundColor = [UIColor whiteColor];
[self.window addSubview:self.navigationScreen.view];
[self.window makeKeyAndVisible];
[self performSelector:#selector(pushSecondScreen) withObject:nil afterDelay:3.0];
return YES;
}
#end
Edit 3: Revisited after noticing that you are speaking mainly about iOS 4.2. I can't currently test it on any iOS 4.2 but I know about a possible workaround. You can always hide the navigation bar in your UINavigationController and just put a separate navigation bar into each screen. You will have absolute control over them and you can even edit them in Interface Builder.
I have a subclass of QLPreviewController that I'm pushing into a UINavigationController. I am able to show/hide the bottom toolbar using:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.navigationController.toolbarHidden = NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.navigationController.toolbarHidden = YES;
}
But items I'm adding to the toolbar aren't showing up:
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *testButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Test"
style:UIBarButtonItemStylePlain
target:self
action:#selector(testButtonClicked:)];
NSArray *myToolbarItems = [NSArray arrayWithObjects:testButtonItem, nil];
self.toolbarItems = myToolbarItems;
[testButtonItem release];
}
Any advice would be very appreciated. Thanks.
Where are you allocating and initializing that view that is giving you the problem?
Are you using a splitViewController on iPad?
From the sound of it an object is sticking around for longer than necessary or when being called back it is not being re allocated and initialized properly before attempting to put it back on the stack, if you are using a splitViewController on iPad it handles it's views differently than a simple UINavigationController on an iPhone.
I have a tab based application I am working on.
I have a view controller named DetailsView.m, with an accompanying nib file called DetailsView.xib. This has a couple of UILabels in, which are linked using IBOutlet to DetailsView.m view controller. When I load this view controller/view using the tab bar controller, it works fine and the UILabels are populated dynamically.
Now I want to load this entire view inside a UIScrollView instead so I can fit more content in.
So I created another view controller called DetailsHolder.m with a nib file called DetailsHolder.xib, and assigned this to the tab bar controller.
I wrote this code below to load the first view (DetailsView) into the UIScrollView in the second view (DetailsHolder). I wrote it in the viewDidLoad method of DetailsHolder:
DetailsView* detailsView = [[DetailsView alloc] initWithNibName:#"DetailsView" bundle: nil];
CGRect rect = detailsView.view.frame;
CGSize size = rect.size;
[scrollView addSubview: detailsView.view];
scrollView.contentSize = CGSizeMake(320, size.height);
This correctly loads the sub view into the UIScrollView, however, the labels inside DetailsView no longer do anything. When I put an NSLog inside viewDidLoad of DetailsView - it never logs anything. It's as if I've loaded the nib ok, but its no longer associated with the view controller anymore. What am I missing here? I'm a bit of a newbie in obj C/iOS (but have many years Actionscript/Javascript knowledge.
Thanks in advance,
Rich
Edit: Contents of DetailsView as requested:
DetailsView.h:
#import <UIKit/UIKit.h>
#import "AppModel.h"
#interface DetailsView : UIViewController {
IBOutlet UITextView* textView;
IBOutlet UIImageView* imageView;
}
#end
DetailsView.m
#import "DetailsView.h"
#implementation DetailsView
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewWillAppear:(BOOL)animated
{
AppModel* model = [AppModel sharedInstance];
[model loadData];
int selectedLocation = [model getSelectedLocation];
NSArray *locations = [model getLocations];
NSArray *data = [locations objectAtIndex:selectedLocation];
textView.text = [data objectAtIndex: 0];
UIImage *theImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[data objectAtIndex: 4] ofType:#"jpg"]];
imageView.image = theImage;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
Essentially all its doing is grabbing selectedLocation (int) from a singleton (which is my model), then grabbing an array from the model, and then trying to insert an image and some text into my view. In my nib file, i have a UIImageView and a UITextView, which I have linked to the two IBOutlets declared in DetailsView.h
When you use the view controller like this, many events will not occur in the view controller, e.g. viewWillAppear, viewWillDisappear and device rotation event. My best guess that you did some initialisation in some of those event methods. Posting your code for DetailsView would make it easier to find the problem.
I'm attempting to load a Modal View Controller (1st) from a Modal View Controller (2nd). While it sounds complicated, it probably isn't.
The 1st controller is actually a UIWebView which is initialized in the loadView method of the .m file:
- (void)loadView {
// Initialize webview and add as a subview to LandscapeController's view
myWebView = [[[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
//CGRect forceframe = CGRectMake(0, 0, 480, 320);
//myWebView = [[[UIWebView alloc] initWithFrame:forceframe] autorelease];
myWebView.scalesPageToFit = YES;
myWebView.autoresizesSubviews = YES;
myWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
myWebView.delegate = self;
self.view = myWebView;
}
Then in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// Load HTML file as an NSURL request
[self.myWebView loadHTMLString:updated_html baseURL:nil];
// Invoke the covering modal view on condition
if (some_condition) {
landscapeCoverController = [[UIViewController alloc] initWithNibName:#"LandscapeCoverController" bundle:[NSBundle mainBundle]];
[self presentModalViewController:landscapeCoverController animated:YES];
[landscapeCoverController release];
}
The intended 2nd Modal View Controller (landscapeCoverController) is initialized with a NIB that I set up in IB.
My intended objective, is to conditionally cover up the UIWebView with the "LandscapeCoverController" view, which will have some buttons and interactivity which will result in the 2nd Modal View being dismissed.
Why isn't my landscapeCoverController loading? Any thoughts greatly appreciated!
Also...the 1st Modal View controller (LandscapeViewController) .h looks like:
#class LandscapeCoverController;
#interface LandscapeViewController : UIViewController <UIWebViewDelegate> {
UIWebView *myWebView;
LandscapeViewController *landscapeCoverController;
}
#property (nonatomic, retain) UIWebView *myWebView;
#property (nonatomic, retain) LandscapeViewController *landscapeCoverController; // Modal view controller
and...the 2nd Modal View controller (landscapeCoverController) viewDidLoad does nothing:
// NIB initialized in LandscapeViewController.m viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
}
as I think the
landscapeCoverController = [[UIViewController alloc] initWithNibName:#"LandscapeCoverController" bundle:[NSBundle mainBundle]];
[self presentModalViewController:landscapeCoverController animated:YES];
[landscapeCoverController release];
statement should handle initialization and loading of the controller...
You are declaring landscapeCoverController as an instance of LandscapeViewController, and allocating it as a UIViewController. This is most likely your problem (probably the first one, as you aren't calling any methods specific to LandscapeViewController). Also, since landscapeCoverController is an instance variable, you don't really need to release it after presentModalViewController. Try to pick more dissimilar class names. It will save you from confusion like this in the future.