I have a segue that works for iPhone code to present the contents of a tableViewController embedded in a Navigation controller.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UINavigationController *nc = (UINavigationController *)segue.destinationViewController;
tvc = (MyTableViewController *)[nc.viewControllers lastObject];
tvc.managedObjectContext = managedObjectContext;
}
tvc is a storyboard item, UITableViewController subclass, embedded in a navigationController. I am trying to find the easiest way to use the same setup on iPad, but present the contents in a popover, with a navigation controller, as opposed to presenting full screen as it's doing now. Else do I need to create a new viewController without using this setup?
I created a custom UIStoryboardPopoverSegue, with the following perform code:
- (void)perform
{
UITableViewController *src = (UITableViewController *) self.sourceViewController;
MyController *dst = (MyController *) self.destinationViewController;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:dst];
UITableViewCell *cell = [src.tableView cellForRowAtIndexPath:[src.tableView indexPathForSelectedRow]];
UIPopoverController *pop = [[UIPopoverController alloc] initWithContentViewController:navigationController];
dst.popoverController = pop;
CGSize size = CGSizeMake(475, src.view.frame.size.height);
pop.popoverContentSize = size;
[pop presentPopoverFromRect:cell.frame
inView:src.tableView
permittedArrowDirections:UIPopoverArrowDirectionUp | UIPopoverArrowDirectionDown
animated:YES];
}
The downside of this is that it requires a seque that is custom to destination view controller. The upside is an easy to implement and support, well functioning popover segue.
I am trying to implement a simple UISplitViewController where when in portrait mode the master view is hidden and appears with a button in a popover.
My AppDelegate is set up like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch.
self.splitViewController =[[UISplitViewController alloc]init];
self.rootViewController=[[RootViewController alloc]init];
self.detailViewController=[[FirstDetailViewController alloc]init];
UINavigationController *rootNav=[[UINavigationController alloc]initWithRootViewController:rootViewController];
UINavigationController *detailNav=[[UINavigationController alloc]initWithRootViewController:detailViewController];
self.splitViewController.viewControllers=[NSArray arrayWithObjects:rootNav,detailNav,nil];
self.splitViewController.delegate=self.detailViewController;
splitViewController.presentsWithGesture = NO;
// Add the split view controller's view to the window and display.
[self.window setRootViewController:self.splitViewController];
[window makeKeyAndVisible];
return YES;
}
in FirstDetailViewController I set up a button like this:
UIImage *buttonImage = [UIImage imageNamed:#"button-menu.png"];
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame = CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
[aButton addTarget:self action:#selector(showNavigation) forControlEvents:UIControlEventTouchUpInside];
aBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:aButton];
self.appDelegate.rootPopoverButtonItem = aBarButtonItem;
showNavigation looks like this:
-(void)showNavigation{
NSLog(#"I am in show navigation and pc=%#",self.popoverController);
[self.popoverController presentPopoverFromRect:self.view.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
When I initially load the app and press the button I get an NSLog of
I am in show navigation and pc=(null)
And the pop over dosen't show. Now when I rotate the device to landscape mode, and then back to portrait, the master view hides and shows as expected, and the button now works in portrait mode.
Here is the code that handles the rotation:
- (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc {
[[self navigationItem] setLeftBarButtonItem:aBarButtonItem];
[self setPopoverController:pc];
self.appDelegate.rootPopoverButtonItem = aBarButtonItem;
}
// Called when the view is shown again in the split view, invalidating the button and popover controller.
- (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
[[self navigationItem] setLeftBarButtonItem:nil];
[self setPopoverController:nil];
self.appDelegate.rootPopoverButtonItem = aBarButtonItem;
}
How do I get the popovercontroller to not be null before any rotation occurs?
Ok turns out I had an if in my willHideViewController which did not include the [self setPopoverController:pc]; line. duh!
I've spend hours on this and can't get it to work. Hope that someone can help me.
I have a UIPageViewController which works perfectly when I add it to my default view when the application starts. This is what I do:
//Step 1
//Instantiate the UIPageViewController.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
//Step 2:
//Assign the delegate and datasource as self.
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
//Step 3:
//Set the initial view controllers.
ContentViewController *contentViewController = [[ContentViewController alloc] initWithNibName:#"ContentViewController" bundle:nil];
contentViewController.labelContents = [self.modelArray objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:contentViewController];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview: self.pageViewController.view];
Now I want to use a navigation controller in my initial view which then pushes the UIPageViewController on the stack, when the user clicks on a button. This works too, but when I'm in landscape mode the UIPageViewController shows only one page (which is horizontally streched) and not two, as it should be. I need to rotate the device to portrait and then back to landscape mode to force the UIPageViewController to show both pages.
I'm adding the UIPageViewController to the navigation controller with:
[self.navController pushViewController:self.pageViewController animated:NO];
When i debug my code I see that without the navigation controller the delegate methods of the UIPageViewController are called on startup. When I push the UIPageViewController on the navigation controller they're not called until I rotate the device.
Does anyone know how to solve this? Thanks in advance for any help/tips on this.
Figured this out with help from this thread.
The key is setting the spineLocation on the pageViewController when you create it through the options dictionary:
options = [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger:UIPageViewControllerSpineLocationMid]
forKey: UIPageViewControllerOptionSpineLocationKey];
And this option should be put inside of a check to see what the current interface orientation is.
In the above mentioned thread, the interface orientation is checked by a ternary operator; I put it in an if-statement so that I can use the same opportunity to tack an extra UIViewController onto the viewControllers array.
If the interface orientation is portrait, 'options' will still be nil when the pageViewController is created, and so it'll go on to create it in portrait mode with only one viewController.
Here's my whole viewDidLoad code in BookViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
_modelController = [[ModelController alloc] init];
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0
storyboard:self.storyboard];
NSMutableArray *viewControllers = [NSMutableArray arrayWithObjects:
startingViewController,
nil];
NSDictionary *options;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
DataViewController *secondViewController = [self.modelController viewControllerAtIndex:1
storyboard:self.storyboard];
[viewControllers addObject:secondViewController];
options = [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger:UIPageViewControllerSpineLocationMid]
forKey: UIPageViewControllerOptionSpineLocationKey];
}
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:options];
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// Set the page view controller's bounds
self.pageViewController.view.frame = self.view.bounds;
[self.pageViewController didMoveToParentViewController:self];
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
I am trying to build a view where a user can select an image and it is retained within that view after navigating away from the view and coming back again. As a simple test app, I have created two view controllers. The first view controller has a forward button and uses a modal segue to go to the second view controller. The second view controller has a back button (modal segue back to first view controller) a Choose button and a Image button. So far I have configured the choose button to use the imagepickercontroller to select an image from the library and display onto the button which works no problem. The issue is that when i press the back button and the forward again back to the same screen the image isn't retained i have to reselect it. The intention is for the user to be able to take a picture or video of themselves performing an action and then select it within this app and have the picture displayed there for future reference.
My coding is as follows:
ViewController.h
#interface ViewController : UIViewController
<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
{
IBOutlet UIButton *selectFromPhotoLibraryButton;
IBOutlet UIButton *displayPictureButton;
}
#property (nonatomic, retain) UIButton *selectFromPhotoLibraryButton;
#property (nonatomic, retain) UIButton *displayPictureButton;
- (IBAction)selectPicture;
#end
ViewController.m
#implementation ViewController
#synthesize selectFromPhotoLibraryButton;
#synthesize displayPictureButton;
-(IBAction)selectPicture
{
if([UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypePhotoLibrary])
{
UIImagePickerController *picker = [[UIImagePickerController alloc]init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:picker animated:YES];
[picker release];
}
}
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo: (NSDictionary *)info
{
UIImage *image = [[info objectForKey:UIImagePickerControllerOriginalImage] retain];
[displayPictureButton setImage:image forState:UIControlStateNormal];
[self dismissModalViewControllerAnimated:YES];
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *) picker
{
[picker dismissModalViewControllerAnimated:YES];
}
Please let me know if you require any of the other bits of code (its only the default app stuff, no further modification).
As looking to your problem
I think you have to store selected image in NSUserDefault as archived data or something like that.
When u come back to second view then check first in NSUserDefault for image is saved or not.
If u got it then assign it to UIImage object by unarchiving.
As I have not implemented it so can't be sure.
I've just set something like this up myself, I have the chosen image immediately placed in a UIImageView.
First, set up your view controller as a UIImagePickerDelegate:
<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
Then wherever you want to init the UIImagePicker, alloc, init and set its delegate to self:
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
[imagePicker setDelegate:self];
[self presentViewController:imagePicker animated:YES completion:nil];
And then I have the image picker storing the UIImage into the ImageView and also into the NSUserDefaults.
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo: (NSDictionary *)info {
UIImage *image = [[info objectForKey:UIImagePickerControllerOriginalImage] retain];
[userDefaults setObject:image forKey:#"image"];
[userDefaults synchronize];
[self dismissViewControllerAnimated:YES completion:^{
[self updatePreviewImage:image];
}];
}
- (void) imagePickerControllerDidCancel:(UIImagePickerController *) picker {
[self dismissViewControllerAnimated:YES completion:nil];
}
I hope this works for you, and anyone else that view this post.
How do I override the back button for just one view (not for all the back buttons present in different views) such that on click of the back button, the root view controller is shown?
You need to replace the backbutton and associate an action handler:
- (void)viewDidLoad {
[super viewDidLoad];
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#”back”
style:UIBarButtonItemStyleBordered
target:self
action:#selector(handleBack:)];
self.navigationItem.leftBarButtonItem = backButton;
}
- (void)handleBack:(id)sender {
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];
}
Found a solution which retains the back button style as well.
Add the following method to your view controller.
-(void) overrideBack{
UIButton *transparentButton = [[UIButton alloc] init];
[transparentButton setFrame:CGRectMake(0,0, 50, 40)];
[transparentButton setBackgroundColor:[UIColor clearColor]];
[transparentButton addTarget:self action:#selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
[self.navigationController.navigationBar addSubview:transparentButton];
}
Now provide a functionality as needed in the following method:
-(void)backAction:(UIBarButtonItem *)sender {
//Your functionality
}
All it does is to cover the back button with a transparent button ;)
It's old, but the correct answer is that:
Instead of pushing your ViewController on top of all the others, you'd better replace the full stack with the rootVC and the new VC only.
Not:
self.navigationController?.pushViewController(myVc, animated: true)
But:
let vcStack = self.navigationController?.viewControllers
self.navigationController?.setViewControllers([vcStack![0],myVc], animated: true)
Like that, on back it will just popToRoot because it's the previous viewController in stack
I had the similar problem and I successfully used the answer given by Sarasranglt. Here is the SWIFT version.
override func viewDidLoad() {
let transparentButton = UIButton()
transparentButton.frame = CGRectMake(0, 0, 50, 40)
transparentButton.backgroundColor = UIColor.orangeColor()
transparentButton.addTarget(self, action:"backAction:", forControlEvents:.TouchUpInside)
self.navigationController?.navigationBar.addSubview(transparentButton)
}
And the function is
func backAction(sender:UIButton) {
// Some sction
}
Another approach is to adopt UINavigationControllerDelegate Protocol.
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
Those methods will let you know when a controller appears but you have to check that controller is the controller you want.
For keeping same appearance you can use :
UIView *leftButtonView = [[UIView alloc]initWithFrame:CGRectMake(-12, 0, 105, 30)];
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeSystem];
leftButton.frame = leftButtonView.frame;
[leftButton setImage:[UIImage imageNamed:#"ic_system_back"] forState:UIControlStateNormal];
[leftButton setTitle:#"title" forState:UIControlStateNormal];
[leftButton.titleLabel setLineBreakMode:NSLineBreakByTruncatingTail];
[leftButton.titleLabel setFont:[UIFont systemFontOfSize:16]];
[leftButton setTitleEdgeInsets: UIEdgeInsetsMake(0, 8, 0, 0)];
[leftButton addTarget:self action:#selector(handleBack:) forControlEvents:UIControlEventTouchUpInside];
[leftButtonView addSubview:leftButton];
UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc]initWithCustomView:leftButtonView];
self.navigationItem.leftBarButtonItem = leftBarButton;
Following Sarasranglt's method of having a transparent button, in objective C, and Pavle Mijatovic's earlier swift version, here is a swift 4 version:
let transparentButton = UIButton()
transparentButton.frame = CGRect(x:0, y:0, width:50, height: 40)
transparentButton.backgroundColor = UIColor.clear
transparentButton.addTarget(self, action:#selector(backAction(sender:)), for:.touchUpInside)
self.navigationController?.navigationBar.addSubview(transparentButton)
and
#objc func backAction(sender:UIButton) {
// Some action
}
without making custom button or without any hack(transparentButton) just override 'viewWillDisappear' method and write your code inside the block.
override func viewWillDisappear(_ animated: Bool) {
// write your code...
}
The following code (written in C# for Xamarin iOS) will replace the back button with a custom one that looks just like the system implementation (chevron icon included).
The system "back" navigation will no longer fire, and you are free to handle the TouchUpInside as you wish.
Converting it to ObjC shouldn't take long, I'll leave that fun to you :)
var backButton = new UIButton(new CGRect(0, 0, 70.0, 70.0));
var symbolCfg = UIImageSymbolConfiguration.Create(UIFont.ButtonFontSize, UIImageSymbolWeight.Bold, UIImageSymbolScale.Large)
var backImage = UIImage.GetSystemImage("chevron.left", symbolCfg);
backButton.SetImage(backImage, forState: UIControlState.Normal);
backButton.TitleEdgeInsets = new UIEdgeInsets(10.0f, 10.0f, 10.0f, 0.0f);
backButton.SetTitle("Back", forState: UIControlState.Normal);
var backBarButton = new UIBarButtonItem(customView: backButton);
NavigationItem.LeftBarButtonItems = new[] { backBarButton };
Use this code to show a custom back button, Note the backBarButtonItem before marking it as a duplicate answer.
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"< back"
style:UIBarButtonItemStylePlain
target:self
action:#selector(handleBack:)];
self.navigationItem.backBarButtonItem= backButton;
Cheers!