How to restore subview of view controller using state restoration - ios

I am using state restoration to restore previous views and data for my app.When a button is clicked in first view controller I am to pushing second view control.In this case I am able to restore second view controller data.But if I add second View Controller view as subview to first view controller when button is clicked in first view Controller, encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: were not called in second view controller.Unable to restore second view controller data.Do I need to do any other configurations to restore subview data ?
//Firstviewcontroller
-(IBAction)moveToNextViewController:(id)sender {
SecondVeiwController *sec_VC=[[SecondVeiwController alloc]initWithNibName:#"SecondVeiwController" bundle:[NSBundle mainBundle ]];
sec_VC.restorationIdentifier=#"SecondVeiwController";
[self.view addSubview:tab_cnt_1.view];
}
//Secondviewcontroller
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.restorationIdentifier=#"Secondviewcontroller";
self.restorationClass=[self class];
}
return self;
}
+(UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
UIViewController * myViewController =
[[Secondviewcontroller alloc]
initWithNibName:#"Secondviewcontroller"
bundle:[NSBundle mainBundle]];
return myViewController;
}

It seems that iOS does not keep track of subviews, you have to encode the SecondViewController and then decode and add it as a child view controller with its view as a subview.
//FirstViewController
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:self.childViewControllers[0] forKey:#"childVC"];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
UIViewController *v = [coder decodeObjectForKey:#"childVC"];
[self addChildViewController:v];
[self.view addSubview:v.view];
}
//SecondViewController
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:self.view.backgroundColor forKey:#"bgColor"];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
UIColor *bgColor = [coder decodeObjectForKey:#"bgColor"];
[self.view setBackgroundColor:bgColor];
}

Related

ViewdidAppear fires for the second VC before the first

2015-02-06 23:23:35.976 a[47551:2124298] second view did load
2015-02-06 23:23:35.978 a[47551:2124298] first view did load
2015-02-06 23:23:36.060 a[47551:2124298] second view did appear
2015-02-06 23:23:36.062 a[47551:2124298] first view did appear
The above describes my problem. I want to do stuff only after the second view appears, but all the events first as soon as I launch the app.
The first view:
#interface AddViewController ()
#end
#implementation AddViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"first view did load");
// Do any additional setup after loading the view.
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"first view did appear");
}
- (IBAction)addBtnPressed:(id)sender {
NSLog(#"add Btn pressed");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ViewController *destVC = [segue destinationViewController];
destVC.destStation = self.destField.text;
}
The second view:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"second view did load");
manager = [[ CLLocationManager alloc] init];
geocoder = [[ CLGeocoder alloc] init];
[manager requestWhenInUseAuthorization];
// [manager requestAlwaysAuthorization];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"second view did appear");
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
NSLog(#"setting destStation %#", self.destStation);
self.destField.text = self.destStation;
self.destStationLoc = [LocationCalc findDestStationLoc:self.destStation stationInfos:[StationDB database].stationInfos];
}
The segue is connected from the button from the first VC.
Am I doing something wrong?

iOS: Add UIView below UINavigationBar

if I try to add a view to the navigation bar in my custom UINavigationController with this code:
- (void)viewDidLoad {
[super viewDidLoad];
self.sv = (MyCustomView *)[[[NSBundle mainBundle]loadNibNamed:#"MyCustomView" owner:self options:nil] objectAtIndex:0];
self.sv.frame = CGRectMake(self.navigationBar.frame.origin.x, self.navigationBar.frame.size.height, self.navigationBar.frame.size.width, heightOfMyCustomView);
[self.navigationBar addSubview:self.sv];
}
and
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[super pushViewController:viewController animated:animated];
viewController.view.frame = CGRectMake(viewController.view.frame.origin.x, viewController.view.frame.origin.y + heightOfMyCustomView, viewController.view.frame.size.width, viewController.view.frame.size.height - heightOfMyCustomView);
}
when I push a controller, the top part of the controller's view is hidden by my custom view I added to the navigation bar.
How can I put the controller's view down below my custom view?
I'd also suggest to take a look at UINavigationItem.titleView property, that gives you an ability to provide your view for navigation bar title area.
In your custom UINavigationController init method assign delegate to itself, like this:
CustomeNavCtrl.m file:
#implementation CustomeNavCtrl
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.delegate = self;
}
return self;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
//Todo:viewController.view layout
//Todo:send MyCustomView to back or something other you like.
}
CustomeNavCtrl.h file:
#interface CustomeNavCtrl : UINavigationController<UINavigationControllerDelegate> {
}

When trying to segue, nothing happens

I have an app I am working on, and I am trying to have the logo on for a bit longer, and fade out/slide out/effect when it's done.
Here's my setup:
The Tab Bar controller is not letting me place an Image View inside it, so I created a view to have it on.
I am trying to have the logo stay on for a bit, fade out, then automatically switch the view (Segue) to the Tab Bar controller.
This is what I get out of it: http://youtu.be/l4jL0BfpR2k
So here's my code:
//
// BDNLogoViewController.m
// Bronydom Network
//
// Created by name on 10/1/13.
// Copyright (c) 2013 name. All rights reserved.
//
#import "BDNLogoViewController.h"
#import "BDNTabBarController.h"
#import "BDNFirstViewController.h"
#interface BDNLogoViewController ()
#end
#implementation BDNLogoViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[UIView animateWithDuration:1 animations:^{
_imageview.alpha = 0;
}];
//BDNTabBarController *viewController = [[BDNTabBarController alloc] init];
//[self.navigationController pushViewController:viewController animated:YES];
(void)#selector(seguePerform:);
}
- (void)seguePerform:(id)sender
{
//BDNTabBarController *myNewVC = [[BDNTabBarController alloc] init];
// do any setup you need for myNewVC
[self performSegueWithIdentifier:#"open" sender:sender];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Yes, "open" is defined as the segue id.
Do you guys have any ideas on how I could fix this?
To fix, add this
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
[self performSegueWithIdentifier:#"open" sender:self];
}
Remove this from your code
(void)#selector(seguePerform:);
// and all other unnecessary segue stuff you had

UINavigationController State Restoration (without Storyboards)

I've been toying around with state restoration. In the code below, the scroll position of the UITableViewController gets restored, however, if I were to tap through into the detail view (pushing an instance of MyViewController onto the navigation stack), when the app restarts, it always returns to the first view controller in the navigation stack (i.e. MyTableViewController). Would somebody be able to help me restore to the correct view controller (i.e. MyOtherViewController)?
AppDelegate.m
- (BOOL)launchWithOptions:(NSDictionary *)launchOptions
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
MyTableViewController *table = [[MyTableViewController alloc] initWithStyle:UITableViewStylePlain];
table.depth = 0;
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table];
navCon.restorationIdentifier = #"navigationController";
self.window.rootViewController = navCon;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
});
return YES;
}
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return [self launchWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return [self launchWithOptions:launchOptions];
}
MyTableViewController.m
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if(self)
{
self.restorationIdentifier = #"master";
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Master";
self.tableView.restorationIdentifier = #"masterView";
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [NSString stringWithFormat:#"Section %d", section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [NSString stringWithFormat:#"%d", indexPath.row];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyOtherViewController *vc = [[MyOtherViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
}
MyOtherViewController.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.restorationIdentifier = #"detail";
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Detail";
self.view.backgroundColor = [UIColor redColor];
self.view.restorationIdentifier = #"detailView";
}
Because you are creating your detail view controller in code, and not instantiating it from a Storyboard, you need to implement a restoration class, so the system restoration process knows how to create the detail view controller.
A restoration class is really just a factory which knows how to create a specific view controller from a restoration path. You don't actually have to create a separate class for this, we can just handle it in MyOtherViewController:
in MyOtherViewController.h, implement the protocol: UIViewControllerRestoration
Then when you set the restorationID in MyOtherViewController.m, also set the restoration class:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.restorationIdentifier = #"detail";
self.restorationClass = [self class]; //SET THE RESTORATION CLASS
}
return self;
}
You then need to implement this method in the restoration class (MyOtherViewController.m)
+(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
//At a minimum, just create an instance of the correct class.
return [[MyOtherViewController alloc] initWithNibName:nil bundle:nil];
}
That gets you as far as the restoration subsystem being able to create and push the Detail View controller onto the Navigation Stack. (What you initially asked.)
Extending the Example to handle state:
In a real app, you'd need to both save the state and handle restoring it... I added the following property definition to MyOtherViewController to simulate this:
#property (nonatomic, strong) NSString *selectedRecordId;
And I also modified MyOtherViewContoller's viewDidLoad method to set the Detail title to whatever record Id the user selected:
- (void)viewDidLoad
{
[super viewDidLoad];
// self.title = #"Detail";
self.title = self.selectedRecordId;
self.view.backgroundColor = [UIColor redColor];
self.view.restorationIdentifier = #"detailView";
}
To make the example work, I set selectedRecordId when initially pushing the Detail View from MyTableViewController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyOtherViewController *vc = [[MyOtherViewController alloc] initWithNibName:nil bundle:nil];
vc.selectedRecordId = [NSString stringWithFormat:#"Section:%d, Row:%d", indexPath.section, indexPath.row];
[self.navigationController pushViewController:vc animated:YES];
}
The other missing piece are the methods in MyOtherViewController, your detail view, to save the state of Detail controller.
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.selectedRecordId forKey:#"selectedRecordId"];
[super encodeRestorableStateWithCoder:coder];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
self.selectedRecordId = [coder decodeObjectForKey:#"selectedRecordId"];
[super decodeRestorableStateWithCoder:coder];
}
Now this actually doesn't quite work yet. This is because the "ViewDidLoad" method is called on restoration before the decoreRestorablStateWithCoder method is. Hence the title doesn't get set before the view is displayed. To fix this, we handle either set the title for the Detail view controller in viewWillAppear: , or we can modify the viewControllerWithRestorationIdentifierPath method to restore the state when it creates the class like this:
+(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
MyOtherViewController *controller = [[self alloc] initWithNibName:nil bundle:nil];
controller.selectedRecordId = [coder decodeObjectForKey:#"selectedRecordId"];
return controller;
}
That's it.
A few other notes...
i presume you did the following in your App Delegate even though i didn't see the code?
-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}
I saw a bit of flakiness when running the demo code in the simulator with it correctly preserving the Scroll offset. It seemed to work occasionally for me, but not all the time. I suspect this may be because really restoration of MyTableViewController should also use a restorationClass approach, since it's instantiated in code. However I haven't tried that out yet.
My testing method, was to put the app in the background by returning to springboard (required for the app state to be saved.) , then relaunching the app from within XCode to simulate a clean start.
the only problem with your code is navigation controller
these two lines
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table];
navCon.restorationIdentifier = #"navigationController";
i dont know why navigation controller never get its restoration class. i had the same problem. i found some alternative solution to create a navigation controller stand alone in storyboard and use that.
here is code
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
UINavigationController* navigationController = [storyboard
instantiateViewControllerWithIdentifier:
#"UINavigationController"];
navigationController.viewControllers = #[myController];
it will work fine .
just follow this http://petr-pavlik.squarespace.com/blog/2013/6/16/stare-restoration-without-storyboard
Since you are doing this in code and not via Storyboard, you will need to provide not only a restorationIdentifier but also a restorationClass to you detail view controller.
You could leave it unassigned in which case -(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder gets called on the application delegate to make the object (view controller with a restorationIdentifier but no restorationClass).
Try the following (please note the UIViewControllerRestoration protocol):
#interface MyViewController ()<UIViewControllerRestoration>
#end
#implementation
- (id)init
{
self = [super init];
if (self) {
// Custom initialization
}
if( [self respondsToSelector:#selector(restorationIdentifier)] ){
self.restorationIdentifier = #"DetailViewController";
self.restorationClass = [self class];
}
return self;
}
#pragma mark - State restoration
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
UIViewController *viewController = [[self alloc] init];
return viewController;
}
#end
Also note that this is a very simple example, usually you might have a lot more interesting things going on in -viewControllerWithRestorationIdentifierPath:coder:

cannot set the text of label in DetailViewController after passing data between different viewController

pass data from FirstViewController to DetailViewController. i can not set the text of label in DetailViewController; FirstViewController is a tableview and it is good.
i use method updateRowNumber to set the rowNumber . and in DetailViewController, i can use debugger to see the rowNumber is correct. but the label's text is not showed on the view.
anyone can help me out?
in my FirstViewController
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (dvController == nil)
{
DetailViewController *aController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
self.dvController = aController;
[aController release];
}
[[self navigationController] pushViewController:dvController animated:YES];
[dvController updateRowNumber:indexPath.row];
}
in my DetailViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController
{
int rowNumber;
IBOutlet UILabel *message;
}
#property(readwrite) int rowNumber;
#property(nonatomic, retain) IBOutlet UILabel *message;
- (void) updateRowNumber:(int) theindex;
#end
in my DetailViewController.m
#import "DetailViewController.h"
#interface DetailViewController ()
#end
#implementation DetailViewController
#synthesize message, rowNumber;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void) updateRowNumber: (int) theindex
{
rowNumber = theindex + 1;
message.text = [NSString stringWithFormat:#"row %i was clicked", rowNumber];
}
- (void)dealloc
{
[message release];
[super dealloc];
}
- (void)viewDidLoad
{
message.text = [NSString stringWithFormat:#"row %i was clicked ", rowNumber];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Do any additional setup after loading the view from its nib.
}
- (void)viewWillAppear:(BOOL)animated
{
//message.text = [NSString stringWithFormat:#"row %i was clicked ", rowNumber];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You'll need to learn how the view is loaded, the process is well described in the documentation
What happens is that the view and all the outlets are nil until the view is loaded, you can make sure it is loaded by calling self.view; before configuring the outlet at updateRowNumber:
Please also note, you are better to call [super viewDidLoad] in the beginning of the overridden viewDidLoad, it makes sense as you need to let UIViewContorller to do it's staff before you do some customized logic, the dealloc is different as you need to do your logic before the standard NSObject -dealloc fires. Hope it's understandable.

Resources