I have two UICollectionViewControllers - lets call them master and slave. The master view controller is configured with layout A and the slave with layout B. I set useLayoutToLayoutNavigationTransitions on master to YES and push the slave onto the navigation stack. The layout changes from A to B and I can navigate < back and see layout A. All is good.
However, in response to the user typing some text into a search box in the master view controller, I need to change the layout of the master collection view controller to layout C. This works as expected, but when I push the slave collection view controller onto the stack (configured with layout B) and navigate < back, I end up back at the master collection view controller with layout A, not layout C, as expected.
So far, I've tried:
putting a symbolic breakpoint on -[UICollectionView setCollectionViewLayout:] (and it's related/sibling methods) to check whether something was messing with the collection view layout before pushing the slave onto the stack. I see the break point getting hit when I expect the layout to change, but nothing unusual in the process of popping the slave collection view controller off the navigation stack.
stopping in the debugger and verifying the type of the collection view's collectionViewLayout property and I can see it is set to an instance of layout C before pushing the slave collection view onto the stack. After navigating backwards, I can see the layout has returned to the type of layout A and not layout C (as expected), which tallies with what I'm seeing visually.
implementing -navigationController:willShowViewController:animated: delegate callback and setting the collection view layout to what's required. However this appears to get over-written.
implementing -navigationController:didShowViewController:animated: and setting the collection view layout, but this results in an infinite loop and errors logged to the console.
Does anyone have any ideas what's going on here and how to solve this? I'm wondering if it's a straight-up UIKit bug, and if so, if there are any potential workarounds.
Related
I have an app which comprises of 2 main controller types:
1) A UITableViewController - which acts as a navigation screen
2) A UIViewController contained within a UINavigationController - which shows the main app content.
The TableViewController content looks a little like this:
Page 1
Page 2
Page 3
... etc
I've defined a property on AppDelegate - an array called PageViewControllers.
When the application starts, a new instance of UIViewController is created for each Page within the app.
The first Page's controller is set as the UINavigationController's rootViewController.
When the user selects a row in the UITableView controller, the UINavigationController pushes or pops to the relevant view controller for that row. (If I tap "Page 3", it pushes to the controller for Page three).
I have this working well - the only problem is the app occasionally crashes when trying to jump far back in the navigation stack. For example, from Page 15 to Page 2
The error message I'm getting is:
*** Terminating app due to uncaught exception 'RuntimeError', reason: 'NSInternalInconsistencyException: Tried to pop to a view controller that doesn't exist.
I think the UINavigationController might be releasing some controllers. I thought the app would hold all of the previous controllers in memory to allow the UINavigationController's back button to behave as expected?
Any idea how I can prevent this from happening or is there something I've missed?
Update
Here's the code where I push/pop the navigation controller after a table row is selected. (It's Rubymotion)
def tableView(tableView, didSelectRowAtIndexPath: indexPath)
# first we need to work out which controller was selected...
page = Page.current
currentPageController = appDelegate.pageControllers[page.absoluteIndex]
currentPageControllerIndex = appDelegate.pageControllers.index(currentPageController)
nextPageController = appDelegate.pageControllers[Page.pageAtIndexPath(indexPath).absoluteIndex]
nextPageControllerIndex = appDelegate.pageControllers.index(nextPageController)
case
# When we're moving forward in the stack...
when currentPageControllerIndex < nextPageControllerIndex
for controller in appDelegate.pageControllers[(currentPageControllerIndex + 1)..nextPageControllerIndex]
# push the next controller on to the nav stack, only animate if it's the last one
appDelegate.rootViewControllerNav.pushViewController(controller, animated: controller == nextPageController)
end
# When we're moving backward in the stack...
when currentPageControllerIndex > nextPageControllerIndex
appDelegate.rootViewControllerNav.popToViewController(nextPageController, animated: true)
# When we select the same step again...
else
NSLog("Selected the same page")
end
# close the side menu afterwards
appDelegate.rootViewControllerNav.sideMenu.toggleSideMenuPressed(self)
end
"I thought the app would hold all of the previous controllers in memory to allow the UINavigationController's back button to behave as expected?"
As long as you're going forward this is true -- as you push a new view controllers, it adds the new one to the previous ones on the stack. But, when you popViewController, it's removed from the stack, and if you don't have a strong reference to it, it will be deallocated. So if you go 1-->2-->3........-->15 and then pop to 2, it should work, but if you do something like this:
1-->2-->3 popTo 1 then push to-->15 then pop to 2, neither 2 nor 3 will exist any more.
So, you should pay attention to what sequence you're picking cells in your table, and note when you get the error. See if it corresponds to this explanation.
Cracked it!
The problem here was in the logic elsewhere in my app. You'll see that page is set from Page.currentPage.
page = Page.currentPage
Page.currentPage should always be set to the page that's currently being displayed.
The code to set current page was in the viewDidLoad method of the PagesViewController instead of the viewDidAppear:animated method - meaning that one a page had been viewed, it wouldn't set Page.currentPage again.
Fixed now - thanks a lot for the ideas and tips shared!
Just as a disclaimer, I am an iOS beginner, in a pretty unique position. I'm an intern learning on the job, with a hard deadline for a test app, with specific specs. So I unfortunately don't have the luxury of learning all that I should about xCode and objective C.
Anyways, I am trying to create a navigation menu similar to the iPad's slide out menu. I've looked at plenty of sample code given in response to questions like this in the past. It all works perfectly fine, but I can't understand all of what they're doing. I think this results from being fairly bewildered by view controllers and delegates. And, since I'm supposed to be writing this all by myself, I can't just build off of their existing code.
My plan for this working is to have one main view controller, containing the navigation menu in a table view. This is hidden behind a normal UIView, until a button is pressed, at which point the normal UIView slides offscreen enough to reveal the menu. Upon selection of a menu item, the normal UIView would slide back to its original position, and be replaced by the relevant UIView, controlled by its view controller. In other words, clicking on the menu item relating to "Home" would load the homeViewController.xib, controlled by the homeViewController. This would be loaded in that normal UIView subview, on top of the tableView.
I'm able to load a view in that normal UIView as a result of a button press.
homeViewController *_homeViewController = [[homeViewController alloc]initWithNibName:#"homeViewController"];
[self frontView] = _homeViewController.view;
There may be some syntax errors in that code, since its from memory. I'm not able to access the computer with my code on it at the moment, but that's the general gist. Basically, that places the home view nib on the frontView (that normal UIView that's over the table view), but its not connected to homeViewController. Or at least I think that's the issue, when I press a button, that's correctly connected between the nib and the homeViewController.h, and only set to NSLog in the .m file, the application crashes.
Any idea how to connect this nib to its viewController when displayed in this way? Or, to create the nib in IB, without the associated .h and .m files, and use the main view controller as the homeViewController as well? Or, if my logic is inherently flawed, what SHOULD I do? Something with NavigationControllers?
EDIT:
I also tried a new approach- changing homeViewController's file owner to viewController, and connecting the button on homeViewController's action to viewController. This too caused a crash upon pressing the button. I really think the issue is with having multiple view controllers acting on screen at once, or having multiple views from separate nibs, controlled by one view controller on screen at once.
I suspect that your immediate problem is that _homeViewController is being freed as soon as you leave whatever method that code is in. To fix this, create a strong property in this class that holds the _homeViewController for as long as its view is needed and allocate it to that property rather than a local variable. That way, buttons (or whatever) that are part of that view still have a valid controller object backing them.
In the long run, pushing or presenting view controllers when you need the screen to change is a much better strategy but, as I said, the immediate problem.... :)
I have a simple iPad application with 5 views. On the first view, the user is asked to make some selections and set some options. From this information, the other 4 views are programatically changed after an NSNotification message is sent to them. (i.e controls are added, updated).
My problem is that when the application is first loaded, the user sees View1, but View2, View3, View4 and View5 have never been opened yet, so any changes I make programatically to those views are not done and when the user navigates to them (via the tab bar) for the first time, no changes are shown.
[EDIT: I should point out that the code for making the changes to each view is contained within the ViewController itself, and is executed when the view observes the incoming NSNotification. When the view is not loaded, it understandably never received the incoming NSNotification.]
Only after the user looks at any of those screens at least once and then goes back to View1 and makes changes, are the other Views updated properly.
I thought I could get around this issue by actively loading Views 2,3,4 and 5 into memory on application start, so that they are ready to begin receiving notifications right away.
Is there an easy way to do this in iOS 5?
Why do the view changes straight away?
I would store an indicator of the changes needed when the users answers the questions on the first view and then apply the changes on -viewDidLoad of each view that needs to be changed.
Instead of trying to load the views into memory, I'd suggest you initialize these views with the options that the user set on the first view. What I usually do in such situations, when I have a global parameters that are used in many places, I create a utility class to keep the data, make it a singleton, then access the shared instance in the viewDidLoad in the views that use the data during initialization.
I have a series of UIViewControllers throughout my application. Most of them have the navigation bar but some of them hide it.
The problem is that sometimes as you transition between a view with or without navbars to another view with or without navbars there is a black box that replaces the navbar during the transition. This problem was discussed here: Hiding a UINavigationController's UIToolbar during viewWillDisappear:
This solution is fine and it does get rid of the black box, but I really don't want what was described as a "Cheshire Cat" disappearance. I've tried myriad solutions using prepareForSegue, ViewWillAppear, viewWillDisappear, etc. The best I can do is change the scenario in which the black bar shows up.
By this I mean, there are four combinations of view transitions between the two navigation bar states (hidden vs. not-hidden):
Hidden - Hidden
Hidden - Not Hidden
Not Hidden - Hidden
Not Hidden - Not Hidden
No matter what solution I've tried, at least one of those combinations results in my black box rearing its ugly head. The problem I have is that I've been unable to find anywhere that I can get a reference to the source view controller and the destination view controller when popping a view off of the navigation controller's view stack.
If I could get both references in the same event, I could simply determine what the combination is and handle the behavior appropriately like I would in prepare for segue.
Now, I know that "it's not possible" is a reasonable (and even a probable) answer, but I won't accept that as a solution alone. If it is indeed not possible, I'd like thoughts on a reasonable alternative. For example, I could handle all view controller popping manually (including the default back button) and thus could get the "upcoming controller" from the navigation controller's stack.
I would just prefer a solution using built in APIs or at least a solution where my controllers didn't have to be aware of their own navigation bar states.
Thanks a lot,
Patrick
I think UINavigationControllerDelegate is what you're after. It declares two methods:
-navigationController:willShowViewController:animated:
-navigationController:didShowViewController:animated:
All you need to do is set yourself as the delegate of the parent navigation controller and implement these methods to be notified of incoming view controllers.
Having said that, I've never needed to resort to this for hiding and showing navigation bars. Strictly speaking, view controllers where the navigation bar will always be visible never touch the navigation bar's visibility. When I'm moving into a view controller where it needs to hide, that view controller is responsible for hiding and setting it back to its prior state before disappearing. Following these standards has proven reliable for me.
I have a custom UIViewController called ProductDetailViewController, it has lots of subviews and each of the subviews' content is populated by data requested from a remote server.
There are two ways you could end up looking at a product detail view: in one tab of a tab bar controller you can browse to a product and one gets pushed onto the stack of the navigation controller specific to that tab, or in the other tab you can scan a barcode and a ProductDetailViewController gets pushed onto a separate navigation controller within that tab.
The strange thing happening is that if you have a product detail up in one tab, and then bring one up in the other, when you switch back to the first tab you see overlapping duplicate subview content, as in there are two labels/product images/tableviews stacked on one another within the one view.
FYI:
Nothing ever goes wrong with the second instance you push onto a nav controller stack, it's always the first one that this happens to, as if subviews were added into the existing controller as well as the new instance. I don't think there are two entire product detail views being stacked - the view has a solid background color, so it would hide the one beneath. I'm quite certain I'm pushing to separate nav controllers. At first I thought maybe the incoming data was being sent to both controllers, but that wouldn't account for multiple instances of UI elements overlapping.
Has anybody ever run into anything like this? It's a first for me for sure, and it's driving me nuts.
Screenshot of overlapping product name label subviews:
Somewhere in your code you have a lot of [myview addSubview:anothersubview]
Probably you duplicating subviews when you updating ProductDetailViewController.
Try to insert next code before u start to populate all subviews
[[myview subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
The issue is that many of the subviews aren't actually created until data is received, and the application makes use of NSNotificationCenter to determine when data has been returned and what class and function should receive it. I should have mentioned the use of NSNotificationCenter initially, but it hadn't crossed my mind that it could be this type of problem.
I send a string to my API response notification class as an identifier, and by changing that from being the same between all ProductDetailViewControllers, to a unique value by concatenating the requested URL to my string, my problem has been solved.