I am converting my view controllers into a split view controller with a Universal Storyboard to show two views on screen at the same time, which will only appear on iPad - only one will be visible on iPhone. I have a situation where a UIBarButtonItem is only relevant when only one view controllers is on screen. I want this button removed or hidden when both view controllers in the split view controller are visible.
I cannot use Size Classes to do this, since the button I want removed is in the master view controller (actually I have button in both the master and detail that should be removed), which won't have a size class of Regular w Regular h. Plus Interface Builder doesn't provide an "Installed" option for UIBarButtonItems, and I cannot check size classes in code because the app will also run on iOS 7.
How can I remove/hide a UIBarButtonItem when both the master and detail view controller are on screen? Or I could not add this button in IB, then add it in code when deemed appropriate, but how would I know if both view controllers will be on screen?
Here's a nice solution that works great for iOS 8 and iOS 7 on iPhone and iPad. You simply detect if there is a split view controller and if so, check if it's collapsed or not. If it's collapsed you know only one view controller is on screen. Knowing that info you can do anything you need to.
//remove right bar button item if more than one view controller is on screen
if (self.splitViewController) {
if ([UISplitViewController instancesRespondToSelector:#selector(isCollapsed)]) {
if (!self.splitViewController.collapsed) {
self.navigationController.navigationBar.topItem.rightBarButtonItem = nil;
}
} else {
self.navigationController.navigationBar.topItem.rightBarButtonItem = nil;
}
}
Related
I've done a lot of research and read a lot about the use of Tab Bar Controllers with Split View Controllers but cannot seem to find any hint of how to solve my problem...
The following post in the Apple Developer Forum for Cocoa Touch under the heading "Place SplitViewController inside TabBarController" has so far given me the greatest lead.
As of iOS 8, embedding a Split View Controller in a tab bar controller
(or your own container view controller) is supported and expected to
'just work'. Pushing a split view controller onto a navigation stack
remains unsupported.
This appears to go against Apple Documentation including this article titled "Combined View Controller Interfaces" dated November 2014.
You can use the view controllers that the UIKit framework provides by
themselves or in conjunction with other view controllers to create
even more sophisticated interfaces. When combining view controllers,
however, the order of containment is important; only certain
arrangements are valid. The order of containment, from child to
parent, is as follows:
Content view controllers, and container view controllers that have
flexible bounds (such as the page view controller)
Navigation view controller
Tab bar controller
Split view controller
I have a UITabBarController with seven tabs. Of these, five tabs lead to UISplitViewControllers and two tabs lead to UINavigationControllers.
Here is a screenshot of some of the storyboard the shows the tab bar controller leading to three of the five split view controllers...
No problem when I run for target with self.traitCollection.horizontalSizeClass = UIUserInterfaceSizeClassRegular - where the horizontal (width) dimension of the device screen is Regular (not Compact) -> running on an iPad. All seven tabs appear across the bottom tab bar and all view controllers, including split view controllers, work perfectly.
My problem?
Xcode spits an error and freezes app operation when I run for target with self.traitCollection.horizontalSizeClass = UIUserInterfaceSizeClassCompact - where the horizontal (width) dimension of the device screen is Compact -> running on iPhone or iPhone Plus. Same outcome, as expected, for both IB and on iOS device.
Error Message: Split View Controllers cannot be pushed to a Navigation Controller <UIMoreNavigationController: 0x7ffda38b0200>
I know why I've received the error. Where the horizontal size class is "Compact", the seven tabs drops to five on screen, including one (specially prepared by iOS) "More" tab. The remaining three tabs are relegated to the "More" tab that is its own navigation controller and table view controller. My storyboard is attempting to push the split view controller onto this navigation stack.
Any thoughts on a legitimate solution?
I would recommend using a custom tab bar controller with a "More" section that does not push view controllers into a navigation controller. Preferably, one that is a subclass of UITabBarController, so you can use it with Interface Builder.
My approach would be to replace the current "More" table view controller with a view controller of your own that shows a list of overflowing tabs, but does not push their corresponding view controllers into the navigation bar when displayed.
One possible strategy is to become the delegate of the UITabBarController's more navigation controller (found in the tab bar controller's moreNavigationController property). Then use a delegate method, such as navigationController:willShowViewController:animated:, to replace the content of the navigation controller's viewControllers array if the view controller to be displayed is anything but your custom "More" view controller.
I haven't tried this, but it seems like a good place to start and does not require too much knowledge of the tabview controller's internals.
I have a Universal app, Xcode 7 Beta, targeting iOS 8.
I am presenting a View Controller using UIPopoverPresentationController.
When presented in any Regular Size Class (e.g. Full screen iPad in any orientation), the View Controller appears as a popover, at the size I have set in the preferredContentSize property. A tap outside the bounds of the View Controller will dismiss the popover.
When presented in Compact Width Size Class (e.g. any iPhone in portrait orientation), the "popover" will become a full screen view, sliding in from the bottom to the top.
The Problem
While an iPad popover has built-in dismissal behaviour (i.e. tap outside of the popover), "popovers" presented full-screen do not. And thus need some UI in order to provide the user with a way to dismiss the popover.
Question
What is the best (recommended?) way to present a UI such that it only appears for the Compact Width Size Class, in order to give the user an option to dismiss the (full screen) "popover" View Controller?
Discussion
One approach I have seen is to embed the [View Controller to be presented] in a UINavigationController. A bar button is added to the Navigation Bar, though whose outlet we can tell the presenting View Controller to dismiss the presentation.
However, not only does this require an extra, unwanted hierarchy for the otherwise simple presented View Controller on the iPad, but it requires environment checking (idiom? size class?) in viewDidLoad to programmatically hide the NavigationBar if a Regular Width Size Class (e.g. iPad) is being used. This strikes me as... fragile.
Another option is to do the above, but use Size Classes within Interface Builder to Install/Uninstall the Navigation elements, non-programmatically.
Thoughts?
Related link, discussing the detection of popover mode
Look at Interface Builder, you can make available any view depending on the Size Class currently running on your app :) If i remember well (because I can't check for now as I'm in a bus) you can specify a view to be available in a specific Size Class in the Property Inspector tab of the selected view. That way you just have to add a button or a navigation bar with items in your view and make it available only for Compact widths :) I'll check more specificaly today to take you some screenshots :)
I'm trying to create a tabbed application with navigation elements inside the tab bar, as seen in the picture below (the red bar) using Swift/XCode 6.2. Basically those three icons in the middle will direct the user to different view controllers. The other two icons would be context-based. For example, on a table view page you would see the menu icon and add new icon as seen in the image. However, clicking on a row would change the menu icon to a back icon, and the add icon to something else.
That's the general idea, but I'm having a very hard time implementing something even close to this. The first issue is that whenever I embed a view in a Tab Bar Controller, I can't move the tab bar to the top. However, when I create a custom UITabView in a View Controller, Control + Click and dragging a Tab Bar Item to another view doesn't create a segue. I haven't even begun to tackle having the navigation elements inside the bar.
I guess what I'm asking is just for a little guidance on what route to take to tackle this. I'm assuming I can't use a Tab Bar Controller or Navigation Controller because it doesn't seem like I can customize them all that much. So custom Tab Bar and Navigation Bars, and then implemnt the segues and button changes programmatically?
Thanks.
I will try to guide you from an architectural perspective (so you won't find much code below).
Using a UITabBarController
In order to achieve what you are suggesting, you are right you cannot use a UITabBarController straight away, among several reasons, the most immediate one is that they are meant to be always at the bottom and you want it in top (check Apple's docs). The good news is that probably you don't need it!
Note: If you still want to go with a UITabBarController for whatever reason, please see #Matt's answer.
Using a UINavigationController
You can use a UINavigationController to solve this task, since the UINavigationBar of a UINavigationController can be customized. There are multiple ways on how you can organize your view's hierarchy to achieve what you propose, but let me elaborate one option:
To customize a UINavigationBar's to add buttons, you just need to set its navigationItem's title view:
// Assuming viewWithTopButtons is a view containing the 3 top buttons
self.navigationItem.titleView = viewWithTopButtons
To add the burger menu functionality on a UINavigationController you can find several posts on how to do it and infinite frameworks you can use. Check this other SO Question for a more detailed answer (e.g. MMDrawerController, ECSlidingViewController to mention a couple).
About organizing your view hierarchy, it really depends on if when the user taps one of the main top buttons, it will always go to the first view controller in the new section or if you want to bring him back to the last view in the section where he was.
3.1 Switching sections displays the first view of the new section
Your app's UIWindow will have a single UINavigationController on top of the hierarchy. Then each of the 3 top buttons, when tapped, will change the root view controller of the UINavigationController.
Then, when the user changes section, the current navigation hierarchy is discarded by setting the new section view controller as the UINavigationController root view controller.
self.navigationController = [sectionFirstViewController]
3.2 Switching sections displays the last displayed view in the new section
This will require a slightly modified version of the above, where your each of your sections will have its own UINavigationController, so you can always keep a navigation hierarchy per section.
Then, when the user taps one of the top buttons to switch section, instead of changing as previously described, you will change the UIWindowroot view controller to the new section's UINavigationController.
window.rootViewController = sectionNavigationController
Using a custom implementation
Of course, the last and also very valid option would be that you implement yourself your own component to achieve your requirements. This is probably the option requiring the biggest effort in exchange of the highest customizability.
Choosing this option is definitely not recommend to less experienced developers.
I'd like to take a stab at this--I think it is possible to use a tab bar controller here.
Your topmost-level view controller will be a UITabBarController with a hidden UITabBar.
Each tab is contained in a UINavigationController.
All view controllers in the navigation controller will be a subclass of a view controller (say, SwitchableViewController).
In SwitchableViewController's viewDidLoad, you set the navigation item's title view (i.e. whatever's at the center; self.navigationItem.titleView) to be the view that holds the three center buttons. Could be a UISegmentedControl, or a custom view.
Whenever you tap on any of the buttons, you change the topmost UITabBarController's selected index to the view controller you want to show.
Issues you may encounter:
Table views inside tabs will have a scrollIndicatorOffset at the bottom even if the tab bar is hidden.
Solution: Play around with the automaticallyAdjustsScrollViewInsets of the tab bar controller, or the inner view controller. https://stackoverflow.com/a/29264073/855680
Your title view will be animated every time you push a new view controller in the navigation stack.
Solution: Take a look at creating a custom transition animation for the UINavigationController.
Is there any way to have a static graphic on screen that doesn't move when pushing / popping view controllers?
The best way I can think to describe this is like a tab bar.
If you have a tab on a tab bar that has multiple pages that swipe left/right, you can view these different pages, and only the main content area moves, while the tab bar stays fixed and doesn't swipe in/out along with the content.
I want to do something similar with a custom toolbar I have created and added to a view, but just now it slides out with the previous viewcontroller and then slides in with the new view controller.
Is there any way to keep that fixed on the screen so that it doesn't move.
Hope that makes sense!
Yes, you can do this using container view.
It is fairly new to XCode, so it doesn't have the widespread use that navigation controllers and tab bars do, but it works similarly.
Basically, You will create a new UIViewController instance in storyboard, and put a Container View inside the view controller, filling the entire view controller. From there, you will right click on the view controller where your flow starts (probably either a tab bar controller or a navigation controller) select embed, and target the container view in your new view controller. Finally, you'll need to change the initial view controller (the arrow that points to the first view controller the app should load) to this new UIViewController that has your container view.
If you run your app at this point, you should notice no difference at all. The entire app is basically running inside the container view of that new view controller. Now, you can a UIImageView to that new view controller anywhere you want. Since the new view controller doesn't ever go away, the UIImageView you put inside it will stay there no matter what the container view is showing.
Hope this helps!
Note: I should add that if your program uses any modal transitions, that will cause the flow to exit the new view controller and it's container view as well, similar to how this affects tab bar controllers.
In my app, i have a main view controller which sometimes brings a modal view on top of it. This modal view is a UINavigationController with a navigation bar. I want to display an image above the navigation bar, and have the navigation bar appear below the image.
I do not want to subclass anything and the app uses autolayout, i do not want a bunch of delegate callbacks and frame calculations. The view inside the navigation controller (the actual modal content) must still respond to different screen sizes correctly, such as rotation, call status bar etc. Also, no IB solutions please, these views are all managed in code.
How do i accomplish this?
I would turn off AutoLayout and place the image at the top
I don't think you can do it with your modal view being a navigation controller. I would do it by making that modal controller a UIViewController that you set up as a custom container controller. You can add an image view to the top of this controller's view and add the view of a child view controller (which would be a navigation controller) to the bottom. This would be a lot easier to do in a storyboard using container views, but it certainly can be done in code.