switching screens and transition screens for an app in objective-c - ios

This is a two part question.
The first question:
So I have an app that transitions from one screen to another, using nib files. heres the code i use to switch screens:
self.transitionView = [[TransitionViewController alloc] initWithNibName:#"TransitionViewController" bundle:nil];
[self.view addSubview: self.transitionView.view];
my problem is i have about 30 - 40 lines of code in the viewdidload method that could conceivably be slowing the transition down slightly, as some of the code uses NSURL to read data in, but its a 2 - 3 second wait between when it enters the viewdidload method and when the view actually finishes loading. the line [super viewdidload] is at the very top of all of this too, which is the most confusing part. so why is my program taking 2 - 3 seconds to load? i think thats a pretty long time considering how little code i have there.
part 2 of the question is i want to create a transition screen that appears for 2 - 3 seconds before the actual screen loads. Ive taken that code out and for some reason my nib file still takes forever to load so i know it has nothing to do with that, but the transition screen needs to pop up BEFORE screen 2 for 3 seconds, even if the app is being opened after 10 minutes. i know there was a function in appdelegate that could assist in this endeavor but im at a loss for what the code should look like. it doesnt seem to be switching with
self.transitionView = [[TransitionViewController alloc] initWithNibName:#"TransitionViewController" bundle:nil];
[self.view addSubview: self.transitionView.view];
and aside from that, im at a loss for an understanding about how the code should be constructed to switch between screens in that fashion. any articles or suggestions anybody can give me?
EDIT
I also tried to separate the NSURL data in the viewdidload method from the method itself. i put it all in a method called initialize, and called it from screen 1 right after calling the 2nd view. it didnt help the switching time at all. the reason i did that is because i didnt want to receive all the nsurl data over again when the person opens the app. i just want them to retrieve it one time and be done with it. am i doing somethign wrong here? this is extremely frustrating

You should not put your transition effects in viewdidload , you should put them in viewdidappear or viewwillappear, that is the the next step that the controller will take after it is loaded. That is your best choice, as for the screen to to put it there, you need to load it using the nib file or in the viewdidload.
I hope that was helpful

Related

Pushing View Controller Fails to Animate

Greeting,
I am struggling with a project, because the segue between a UITableViewController (which lives in a navigation controller) and a custom UIViewController is not animating.
The checkbox in Storyboard clearly states "animates", the kind is "Show (e.g. Push)". Yet, when the segue is performed, there is no animation. After some testing, I have found that manually calling UINavigationController pushViewController(destination, animated: true) does not animate either.
The destination View Controller contains a couple views that do some custom drawing.
What could I be doing wrong ?
Cheers,
Alexandre.
#AlexandreCassange and I managed to find the problem in chat, but for anyone who is finding a similar problem here was the solution:
The problem was that while the viewController was being loaded (before viewDidAppear() is called) some code for laying out a CALayer was taking a too long and effectively blocked the animation.
The process to debug this problem is as such:
Check for any code that runs before viewDidAppear() that may take a long time and comment it out, then check if the animation is working. Do this one block of code at a time so you can find exactly where the offending code is. This is how Alexandre found it himself.
Alternatively, use Instruments (time profiler) to check for any long running code that occurs during the initialisation of the destination viewController.
Sometimes though this could happen because the viewControllers initialisation is complex, and while each individual code block runs relatively quickly, together the process is too slow. In this case use the time profiler to optimise your initialisation. I recommend watching the WWDC video Profiling In-Depth

Pre-render view controller before calling pushViewController

I have AViewController and want to push BViewController on to the navigation stack but BViewController has a lot of UI, which takes two seconds to render.
Is it possible to pre-render the UI of BViewController before it is pushed so that it will appear immediately?
No UI should require 2 seconds to be pushed, ever, so you indeed have a problem.
You have different solutions :
Push the controller without the data loaded, and start loading straight away, while showing a spinner. For example, if you have a tableview, it will be empty, and a spinner will spin in the middle of the screen with a little label saying "Loading...". When it's done, you populate the tableview and display it.
This way, your user knows what is going on and he can decide to go back if he wants to, or just wait the 2 seconds.
If it's really mandatory that the data is loaded as the user pushes, you can load it before he pushes the controller. But this really depends what kind of data it is. If it is detail view of an element, you can't load all the elements just in case. If it's a view that is always the same, you could load it when AViewController has finished loading, which will give you the time required to load because the user will probably stay a second or two before pushing BViewController. This method is probably not recommended. You could achieve that roughly like this :
BViewController *vc = [[BViewController alloc]init];
// [vc viewDidLoad]; I'm not sure this is necessary
But seriously don't go for option 2, it's most probably bad to preload the UI in many many cases. Preloading data is common, but UI, I don't think so.
You could reduce the amount of data you want to load. Make sure your images are compressed enough (on a small screen you don't need 4K images), make sure you're not loading a complete database when you really want a single object, etc. You should only download what the user is gonna see, not 'everything'. Again, if you've done that properly it should be already good.
I strongly suggest you try for option 3strong text, make sure everything is small and exactly what you need. If yes, then go for option 1.
Don't forget : the user knows what he wants, you don't. If he does not want to wait two seconds, he should be able to go back while it's spinning. He should NEVER be forced to wait two seconds, and your app should never be hanging.
EDIT : You said you need to load "A lot of UI". Could you show us/tell us exactly what UI takes two seconds to load? I've had pretty complex views (and I mean really complex), and UI never took that long to load.
Do your loading stuff in bViewController within background thread.
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
// Do your loading stuff here
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run your UI Updates
});
});
}

View Controller not Displaying Correctly

I must be missing something really basic here, but I don't even know how to simply describe the problem. I designed my view controller layout in my storyboard, just a bunch of image views, labels, constraints, etc. All static - the only change the code makes is to add round edges to the buttons (UIViews), but I've also tried without that code with no difference made.
Once my segue is followed to the second view controller, it displays like this on the iPhone:
I've gotten it to display properly in 2 cases (most of the time):
Waiting. Anywhere between 30sec to 10min. Very unpredictable
Pressing the home button then resuming the app. Sometimes the text still won't show up when I do this.
Either way, this is what it's supposed to look like:
It doesn't lag or anything as the UI is completely usable even when it's not displaying properly. The images are not very large (biggest is around 300x600) so I doubt that is an issue. Image size also wouldn't account for why the text isn't displaying either. Do UIImageViews load their images asynchrounously?
I thought it might have been that the views needed refreshing because of the exit/resume behaviour. I tried [eachView setNeedsDisplay] with no success.
Any ideas?
-- UPDATE 1 --
Here is an image of my storyboard. As I said, everything is highly static. I have no idea why it's not displaying.
-- Update 2 --
I tried adding my image files to a bundle as suggested:
Then tried to reference them pragrammatically in the viewDidLoad method:
// Interface
#property (weak, nonatomic) IBOutlet UIImageView *backgroundImage;
// viewDidLoad
self.backgroundImage.image = [UIImage imageNamed:#"background.png"];
No difference unfortunately.
I have had this issue using an xcassets file. It tends to work just fine if I just add them to the bundle directly in a group. Check out this thread for some more details.
Impossible to load an image in xcassets on bundle
Here's what the problem was for those having the same issue. The previous view controller to the problem screen was a login screen. After receiving credentials, the controller would asynchronously hit the server to validate them. The response from the server was then handled in a callback function called by the networking thread. This meant that the segue was being invoked off of the main thread. Oddly enough, the segue still performed fine I guess it just took a while for the main thread to realize what was happening.
TLDR: Make sure to performSegue on the main thread.

UIRefreshControl Inside of UITableView Causing App to Freeze on Rotation - iOS 6+

Ok StackOverflow People...I've got a very interesting problem that I've been trying to solve for days and can't figure out so I need some major help. This will most likely be a very lengthy description but please bear with me and thank you deeply in advance for reading all of this because the more words I have, the clearer I can describe the full picture to you all. I will do my absolute best to be as terse and coherent as I can possibly be. Please let me know wherever I fall short.
Here's the context of my problem: I'm using Storyboards for my iOS app and for a particular nav tab in my app, I had to create two separate scenes for both the Portrait and Landscape orientations. The reason for doing this (instead of say, using Autolayout), is because within this said tab, there are visual elements (table views, web views, etc.) that are laid out differently depending on the orientation and it was a lot easier to create a separate orientation scene to handle this change in the UI instead of doing it programmatically -- (it's also just a lot easier to understand and cleaner code-wise). So the take away to keep in mind from all of this is that these two separate Portrait and Landscape scenes represent the SAME TAB in my app. (Side Note: these scenes were made in the IB of course)
Now the visual elements that I mentioned in the UI earlier -- going deeper, they are all containers for different UIViewControllers. I sandboxed everything in the app and pretty much have a 1-to-1 relationship for all things so these containers will map to my subclassed UIViewControllers that I've created for their specific purposes -- but it's here that the first caveat of my problem arises. Here's a practical example for a clearer picture, I have one UIViewController that contains a UITableView called MXSAnnouncementsViewController and this same view controller exists in both the Landscape and Portrait scenes. I did not create an explicit Portrait or Landscape VERSION of that view controller but instead, have the controller keep track of two IBOutlet properties (tableViewLandscape and tableViewPortrait) that point to the orientation-specific UITableViews -- and this approach works perfectly fine. Moreover in my MXSAnnouncementsViewController, I have a local property called tableView that abstracts the orientation-specific table views. It gets set within viewDidLoad which you can see below:
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.tableViewPortrait) {
self.tableView = self.tableViewPortrait;
} else {
self.tableView = self.tableViewLandscape;
}
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
if (![MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements) {
[MXSAnnouncementManager loadModel:#"MXSAnnouncementGroupAllAnnouncements" withBlock:^(id model, NSError *error) {
if (!error) {
self.arrayLatestAnnouncements = [MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements;
[self.tableView reloadData];
} else {
// show some error msg
}
}];
} else {
self.arrayLatestAnnouncements = [MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements;
}
[self setupPullToRefresh];
}
Whenever I'm in the tab, one of the two orientation-specific IBOutlets is always active and has an address in memory while the other is nil. Whenever I rotate, the roles reverse -- whatever had an address in memory previously is now nil and the other has been initialized and allocated which is why I do what I did with the tableView property in the snippet above. Here is where caveat #2 comes into the picture and it's a doozy -- it has to do with the view lifecycle. Here's a practical example for clarity sake: Say I load the app up in Landscape orientation. When I do, my tableViewLandscape outlet has an address in memory and my tableViewPortrait outlet is nil. That's the expected and desired behavior. Now, when I rotate the app, the crazy stuff begins. Here's one place where I need clarity from all of you with regards to instances of UIViewControllers and what's normal vs. what's not so read the following VERY slowly and carefully.
Rotating the app immediately causes the opposite orientation scene (another INSTANCE of MXSAnnouncementsViewController???) to call its viewDidLoad method (in this example, we're in Landscape so the Portrait scene invokes that method). In that method, my local tableView property gets set to the currently active table view for that orientation (see snippet above). When that method finishes, the previous LANDSCAPE instance of MXSAnnouncementsViewController invokes its viewWillDisappear method which is then followed by the PORTRAIT instance's invocation of its viewWillAppear method which then lastly ends with the LANDSCAPE instance calling its willRotateToInterfaceOrientation callback -- that's the order of operation that I'm seeing from the breakpoints. I really do hope you got all of that because my mind just blew up from it all.
If you're still with me at this point, thank you because we're finally at the home stretch. As the title of this post suggests, the problem I'm trying to solve is my app freezing on rotation. If you haven't noticed on the viewDidLoad snippet, the last instruction to get executed is the setupPullToRefresh method which is the following:
- (void)setupPullToRefresh
{
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refreshTableView:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
}
Since I already explained the whole view lifecycle order of operations on rotation earlier, to make a very long story short, if I comment out that last setupPullToRefresh instruction at the end of viewDidLoad for MXSAnnouncementsViewController, my app works fine. If I include that instruction, my app becomes totally unresponsive on the first rotation and I cannot for the life of me figure out why. Not sure if I'm dealing with an edge case here or something. Any and all insights are welcome and THANK YOU SO MUCH for reading all of this!
Your best approach is probably to abandon your current design of having two separate controllers for portrait and landscape. On iOS, you should always relayout the views for the orientation you want to be in, not destroying and recreating everything. By trying to handle it by recreating everything, you're just going to get yourself in trouble I think.
You can use auto layout to do complex reorderings of views upon rotation if you know it well, but probably your best bet is to scrap your current code to do landscape, and write code to simply rearrange the views yourself upon rotating. You'll have far fewer issues down the road, and your code will be easier for others to understand and maintain as well.
When you remove that one bit of code, your app may appear to be working just fine, but there is probably something going on behind the scenes that isn't quite correct that could come back to bite you in the future. That's probably why adding the line of code breaks it.
Try to add it after rotation
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[self setupPullToRefresh];
}
If that doesn't help, create UIRefreshControl only once and set it to the right table on rotation.
If that doesn't help too, follow the first given answer (#Gavin's answer) and create only 1 table on viewDidLoad and relayout things in -(void)viewWillLayoutSubviews

Loading xib file with date pickers slow

I have a xib file with 2 date pickers in it a few labels and a segment control. I was loading the xib's associated view controller into a popover and it was extremely slow loading only on the first time you open it(~5 seconds to load). So I put a reference to the view of the date picker controller outside of the open popover method and in its parents init method and sure enough loading the parent view controller is slow on load now and the open popover method goes fast. So I came to the conclusion it is the xib file connecting outlets/loading that is slow.
Removing everything from the date picker controller's viewDidLoad and init methods did not help.
Removing 1 date picker improved the speed considerably and adding more date pickers didn't appear to effect the speed at all.
Happens on both ios 5.1 and 6.0.
I can get around it by just dispatching another thread to load it on start but does anyway have a clue why this would be slow? I have seen other apps use multiple date pickers in one view with no trouble.
Solution was just to load it when the app starts up and keep reusing the same controller it will cause a small start up delay but its better than unresponsive controls, so in your app delegate call something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//only use this one controller, allocing another one will be just as slow
self.datecontrollerwith2pickers = [[datecontrollerwith2pickers alloc] init];
//forces xib to load
self.datecontrollerwith2pickers.view;
}
The slow loading is most definitely a bug in Apple's code somewhere.

Resources