Is is possible to have a UIView preloaded so that it will load faster when the user taps on button to load it? Currently I've got a library of informaiton that I'm attempting to load when the user taps a button, and for now it seems to be "ok" , but it makes the navigation to the page choppy, because of all the information in the library it's loading.
Thanks in advance!
It should be possible to split the setup of a View Controller from code that displays the view after a button is pushed. This will eliminate the lag when the button but the task to setup the view controller still need to be done sometime during execution (You can for example put it in the ViewdidAppear method so it is executed while waiting for the button to be pushed.
Take this code for example:
-(IBAction) button_pushed {
/*setup */
NewVC *vc = [[NewVC alloc] init];
vc.var1 = var1;
vc.var2 = x;
[vc setup];
/*display */
[self.navigationController pushViewController:vc animated:NO];
return;
}
You can split the code that setup the view from the code that displays the view
into :
#synthesize vc;
…..
- (NewVC) setup {
//setup
NewVC *vc1 = [[NewVC alloc] init];
vc.var1 = var1;
vc.var2 = x;
[vc setup];
return(vc1);
}
-(void) ViewDidAppear {
if (setupready) {
vc = [self setup];}
return;
}
-(IBAction) button_pushed:(ID) sender {
//display
[self.navigationController pushViewController:vc animated:NO];
return;
}
Related
I have a UIButton in my app that when pressed it shows the next view controller. Sometimes the UI locks up and the app freezes for a moment due to background processes. When this happens the user might tap the button multiple times because nothing happened immediately on the first tap, and when this occurs the UINavigationController pushes the ViewController again a bunch of times on top of itself, so that you have to go back several times to get back to home. Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
self.pushVCButton.multipleTouchEnabled = NO;
}
- (IBAction)pushVCButtonPressed:(id)sender {
self.pushVCButton.enabled = NO;
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
self.pushVCButton.enabled = YES;
}
How do I get this to never push multiple instances of viewController?
You should really try to make the background process run not on UI thread, but if you can not try setting the button enabled to yes only when view did disappear or listen for completion of push animation:
- (IBAction)pushVCButtonPressed:(id)sender {
self.pushVCButton.enabled = NO;
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
// Hack: wait for this view to disappear to enable the button
//self.pushVCButton.enabled = YES;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated]
self.pushVCButton.enabled = YES;
}
Also make sure that the action is not called twice.
I have a trio of functions and a property that I use to control my popovers as follows:
-(void)dismissPopoverIfPresentAnimated:(BOOL)animated
{
if (self.currentPopover)
{
[self.currentPopover dismissPopoverAnimated:animated];
self.currentPopover = nil;
}
}
-(void)presentViewController:(UIViewController *)viewController inView:(UIView *)view fromRect:(CGRect)rect suppressArrow:(BOOL)suppressArrow
{
//Did the user just tap on a button to bring up the same controller that's already displayed?
//If so, just dismiss the current controller.
BOOL closeOnly = NO;
if (self.currentPopover)
{
UIViewController *currentController = [self.currentPopover.contentViewController isKindOfClass:UINavigationController.class] ? ((UINavigationController *)self.currentPopover.contentViewController).topViewController : self.currentPopover.contentViewController;
UIViewController *newController = [viewController isKindOfClass:UINavigationController.class] ? ((UINavigationController *)viewController).topViewController : viewController;
if ([currentController isKindOfClass:newController.class])
closeOnly = YES;
[self dismissPopoverIfPresentAnimated:NO];
}
if (!closeOnly)
{
self.currentPopover = [[UIPopoverController alloc] initWithContentViewController:viewController];
self.currentPopover.backgroundColor = [UIColor whiteColor];
[self.currentPopover presentPopoverFromRect:rect inView:view permittedArrowDirections:(suppressArrow ? 0 : UIPopoverArrowDirectionAny) animated:YES];
}
}
(instancetype) initWithContentViewController:(UIViewController )viewController
{
self = [super initWithContentViewController:[[UIViewController alloc] init]];
if (self)
{
UIViewController contentViewController = super.contentViewController;
[contentViewController addChildViewController:viewController];
[viewController didMoveToParentViewController:contentViewController];
[contentViewController.view addSubview:viewController.view];
[self setPopoverContentSize:viewController.preferredContentSize animated:NO];
}
return self;
}
This runs fine in iOS 7, but in iOS 8 the problem is there is a delay between the call to presentPopoverFromRect and when the item actually shows up onscreen. So, if a user double taps a button to show a popover, the first tap will properly dismiss, then "start" the showing of the new controller. The second tap will make the dismiss call (the popover is not yet visible) and then not show the new controller (this is a design feature so that click a button will show a popover, clicking it again will hide it).
The problem is that the call to dismiss the popover doesn't actually work and the popover will show up. At that point I can't get rid of it because my property is nil and I think it is not showing.
My guess is this is an iOS 8 bug where the dismiss somehow doesn't see a visible popover and thus doesn't do anything, where instead, it should prevent it from showing up.
Oh, one last note is that the call to presentViewController is always done on the main thread.
I'm using ICETutorial with cocoapods.
I'm using it in a SettingsViewController where you can view the tutorial in settings.
// SettingsViewController.m
Tutorial2ViewController *vc = [[Tutorial2ViewController alloc] init];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController pushViewController:vc animated:NO];
And Tutorial2ViewController inherits from ICETutorialController
#interface Tutorial2ViewController : ICETutorialController
ICETutorialPages have buttons that will trigger a callback. It takes in a block. So in my implementation, I have this:
- (id)init
{
ICETutorialPage *layer1 = [[ICETutorialPage alloc] initWithSubTitle:#"Page 1" description:#"Page 1" pictureName:#"Tutorial1_640x1136.png"];
NSArray *tutorialLayers = #[layer1];
self = [super initWithNibName:#"ICETutorialController_iPhone" bundle:nil andPages:tutorialLayers];
__weak Tutorial2ViewController *vc = self;
[self setButton1Block:^(UIButton *button){
NSLog(#"Button 1 pressed.");
[[vc.navigationController topViewController] dismissViewControllerAnimated:NO completion:nil];
}];
if (self != nil)
{
}
return self;
}
The reason why I put all the code in init is that I don't want SettingsViewController to know anything about how the Tutorial2ViewController works. Settings should alloc and init, push to the navigation controller stack and the Tutorial2ViewController should know how to handle itself.
I do get the NSLog that button1 is pressed but the view controller does not dismiss itself and return me to the SettingsViewController.
I will contact the creator of the library and ask him/her to see this question also. I feel that this is me not misunderstanding blocks, navigation controllers, cocoapods, etc...
Thanks
Just try [self.navigationController popViewControllerAnimated:YES];
I am trying to get a UITableView to show up after I tap a next button, I can't get it to work...
I don't know why TimesViewController.m isn't working, how do I link it...
-(IBAction)nextView:(UIBarButtonItem *) sender{
[self pushViewController:TimesTableViewController.m animated:YES];
}
You need to read through View Controller programming guide. Essentially, it depends more on your application design. The most common ways of doing this are:
Tap on nextView should present TableView controller modally.
Tap on nextView should push TableView controller in the navigation stack.
With either of the approach, you need to create a separate view controller for your table view which will manage the table view and act as delegate/data source for it.
From within your button action handler you need to do it this way:
-(IBAction)nextView:(UIBarButtonItem *) sender{
MyTableViewController *tableController = [[MyTableViewController alloc] init];
// For #1 above
[self presentViewController:tableController animated:YES completion:^{
// Any code that you want to execute once modal presentation is done
}];
// For #2 above
[self.navigationController pushViewController:tableController animated:YES];
}
EDIT:
Create a initialize method on your MyTableViewController and pass the values while calling it.
- (id)initWithData1:(NSString *)iData1 data2:(NSString *)iData2 {
if ((self = [super init])) {
self.data1 = iData1;
self.data2 = iData2;
}
return self;
}
MyTableViewController *tableController = [[MyTableViewController alloc] initWithData1:#"Some String" data2:#"Another string"];
PS: You could also expose the string property in your header file of MyTableViewController and set it from the callee class
or if you wanna do it programmatically, in your viewDidLoad, after you created your button
[firstsessionButton addTarget:self action:#selector(showtable:) forControlEvents:UIControlEventTouchUpInside];
-(void)showtable:(UIButton *)sender{
TableViewController *tableViewController=[[TableViewController alloc]init];
[self.navigationController pushViewController:tableViewController animated:YES];
}
I'm confused. I have a navigation controller with a BarItem which opens a first view. After some work is done, I want this view to disappear and I want a second view to open.
root view: navigation controller
first view: activity indicator, where some data is put together
second view: MFMailComposeViewController
In the root view, the BarItem runs these lines to open the first view:
IndicatorViewController *indicator = [[IndicatorViewController alloc] initWithNibName:#"IndicatorViewController" bundle:nil];
indicator.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:indicator animated:YES];
The first view (IndicatorViewController) does some work and finally runs
[self dismissModalViewControllerAnimated:YES];
This works fine. But - how do I open the second view?
I tried this:
I open the second view. After closing the second view, my first view pops up again (since it is still there) and get's dismissed at this point. This code is placed in the first view:
- (void) viewDidAppear:(BOOL)animated {
static BOOL firstTime = YES;
if (firstTime) {
//do stuff that takes some time (that's why I show the indicator)
MailViewController *controller = [[MailViewController alloc] init];
if (controller)
[self presentModalViewController:controller animated:YES];
firstTime = NO;
} else {
[self dismissModalViewControllerAnimated:YES];
}
}
Since the first view pops up again, the user can see the indicator one more time, after the second view is closed - and that is not what I want.
What am I missing here? What would be the better way to do this?
I would do something like this. Make a navigationController, and make the first view as the root controller. Then do something like this:
FirstView.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
}
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
Then in the second view:
SecondView.m
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
And finally in the rootview:
RootView.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *navStack = [NSArray arrayWithObject:self];
self.navigationController.viewControllers = navStack;
[self.navigationController setNavigationBarHidden:NO];
}
This will make your RootView the new rootview of the NavigationController.
self.navigationController.viewControllers
is the array with all the ViewControllers that are on the navcontrollers stack. The first object is the rootcontroller. If we replace the whole array with our own array, it knows only one item. You CAN go back by dismissing if that's what you want though. This isn't the prettiest way of doing it, but it's not the worst either.