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
Related
I'm scratching my head for days already trying to understand what I observe. I'm doing iOS development for many ears and I believe I have never seen such effect:
Basically for all ViewControllers I have in the app (defined in a Storyboard), when the view appears, the initial storyboard defined content/layout is displayed for a second and then the proper content appears.
I'm doing all UI element setup is viewDidLoad() so I expect when content appears it must be already properly configured.
No idea what it can be. Changes in Swift/Xcode? Some hidden project configuration?
sounds to me like you are displaying your vc too soon,
it might be that your initial setup is too costly, or that you are calling a service to get that initial data and until the response arrives your vc is still 'flickering' as you put it.
this is usually solved by presenting a loader and making the actual transition only once you are finished with the initialization / data getting phase.
some initializations are more costly than others.
In Swift, is it better to run VC initialization code in prepareForSegue or in viewDidLoad if it is a viable option when maximizing frame rate is the goal?
There are many times I can choose to setup a vc by passing in an enum that tells it what vc it is and sets itself up accordingly during viewDidLoad. I could instead directly setup these values inside of prepareForSegue minimizing the work during viewDidLoad. Assuming I need to run this code on the main thread, for the smoothest UI transition, which is preferable?
If you want to strictly follow the principles of object oriented programing (and I advise you to) each object must take care of its own internal initialization and setting up and what not. As to which option is going to create a smoother user interface transition, it really does not matter since both options must run on the main thread (UIKit must run on the main thread).
I realized after posting this question that it was in ignorance because both prepareForSegue and viewDidLoad happen prior to viewDidAppear and therefore will delay UI the same amount if the both have code in them that takes place on the main queue regardless of where the code is at. So there should be virtually no difference under nearly all conditions I can currently think of.
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.
Now, yes, there are hundreds of questions (and answers) of how to perform custom segues. However, and I'm no exaggerating, ALL of these answers are wrong (all 50+ I've seen)! Sorry, this might sound harsh, but the truth is, NONE of the suggested answers gives the same (correct) result as Apples built in transitions do (vertical cover etc.).
To be more specific, this is the result that's expected (confirmed with logs):
Segue begins (adds view to hierarchy, invokes viewWillAppear on destinationVC and viewWillDisappear on sourceVC and starts animation).
animation is performed for the whole duration
Segue ends (animation finished, sets the destinationVC as the current VC, either on stack or modally presented. Invokes viewDidAppear on destinationVC and viewDidDisappear on sourceVC).
In short: invoke viewWillAppear/Disappear -> animate transition -> invoke viewDidAppear/Disappear
Using apples built-in segues, this is the expected behavior but somehow not a single soul except me have had issues with this. A lot of versions even add the destination-view as subview first, animates it then removes it again and calls
[srcVC presentModalViewController:destVC animated:NO];
or
[srcVC.navigationController pushViewController:destVC animated:NO];
causing the view-events to be sent in all kinds of random order (same issue with CoreAnimations).
In my case, all I really want is the "Vertical Cover"-transition reverted (top to bottom), with all other events sent EXACTLY as expected (shown above).
So, am I just supposed to live with all kinds of ugly workarounds (doing my "tasks" in hard-coded methods called whenever I need them to etc.), or is there some hidden proper way of doing this in a reusable manner?
Funny thing: Even apple suggest that you do it the "wrong" way, making it seem like the right way but with inconsistent outcome compared to their own ways… So my best guess is that apple do this under the hood, and forgot to give enough flexibility for clients to perform the same operations (big flaw in other words), or that I'm just on some bad trip and see some issue that doesn't exist…
Okay, this might not be a true answer of how to solve it for custom segues (subclassing UIStoryboardSegue), but it does solve the general issue!
After some casual reading about new features, I stumbled upon a new way to do custom transitions between ViewControllers introduced in iOS7 called nothing more than "Custom Transitions" i guess!
Read about it here and here, or video from WWDC here.
I've just dipped my toes, but basically it is a new, closer to the system way of doing transitions and allows for better control = better looking transitions. After glancing at the example provided by the blog I referenced (git found here) I can confirm that FINALLY, we are able to do transitions which behave as ONE EXPECTS THEM TO with events fired at the expected occasions!
Since I'm just reading about it I can't give a thorough explanation yet, but check out the links :)
Note: This is maybe not supposed to completely replace custom segues, but can be used in a similar fashion (check examples) so if you need that little extra fancy transition, this is definitely the way to go by the looks of it! Basically you setup segues in the storyboard, and hook up the correct transition-delegates in the prepareForSegue:-method.
Since I converted an old app to iOS 6 I've started getting the following message in my console.
WARNING: Slow defaults access for key ClientState took 0.023656 seconds, tolerance is 0.020000
Other than updating my code from iOS 5 to iOS 6, I also switched over to auto-layout. I've run Instruments/Time Profiler and the rootViewController in my appDelegate is the problem. Everytime I switch view controllers it sucks the vast major of the time, (regardless of whether I have to instantiate the view controller or re-using one which already exists).
window.rootViewController = myViewController;
I know what the method does superficially, but I'm not sure what happens under the covers... what would cause it to be slow now and what can I do to speed it up?
EDIT: I've tried taking my storyboard off auto-layout and the problem vanishes (of course my UI layout is in shambles). So the obvious conclusion is, it's something about auto-layout. I've probably just under 70 views all combined on the screen and the various constraints needed to lay them out. I have a hard time believing auto-layout is that much slower (from ~80ms with auto-layout turned off to ~1370ms with auto- layout turned on).
Having 70 views on-screen sounds like a lot! My proposal is to make it simpler in some way:
Do you REALLY need all 70 views at the same time?
Check if all views need autolayout, remove it where-ever possible
Can some views be replaced by graphics? I've used views e.g. for shadows, might have been images
Can you split storyBoard into several smaller ones e.g. one for login, details, edit mode etc. Part of the slowness might come from system having to deal with (too) big storyBoards.
Consider creating a new project with 2 view controllers and test the switching speed. Every iOS app has a window, a root view controller and a view controller. The problem isn't likely to be as narrow and clear-cut as you may be hoping. What does each view controller load? Did you inspect the underlying code? Does app delegate do anything on initialization or change of root view controller?