I want to add a sixth tab bar item in my project. When I try that I am getting the "More" tab but when I am clicking on the "More" tab nothing happens. How can I fix this?
In swift, subclass UITabBarController and implement:
override var traitCollection: UITraitCollection {
let realTraits = super.traitCollection
let lieTrait = UITraitCollection.init(horizontalSizeClass: .regular)
return UITraitCollection(traitsFrom: [realTraits, lieTrait])
}
As of iOS 9 (and possibly 8), there is a simple way to do this. I do not recommend shipping apps using this trick - six tabs is pretty cramped on a 4s or 5 - but it's neat to know.
Notice that iPad tab bars can have more than five tabs just fine without a "more" item.
Apple's new way of determining "tablet or not?" is with size classes.
So, subclass UITabBarController and implement:
-(UITraitCollection *)traitCollection
{
UITraitCollection
*realTraits = [super traitCollection],
*lieTrait = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
return [UITraitCollection traitCollectionWithTraitsFromCollections:#[realTraits, lieTrait]];
}
Add six child view controllers to it, and voila! It looks like Apple practices what they preach, in this case!
(* Note that this will also lie about the horizontal size class to the view controllers in the tabs. You may wish to call - setOverrideTraitCollection:forChildViewController: at an opportune time, possibly in an override of setViewControllers:, to "override" their trait collections back to the real one. That way they won't get confused and try to display inappropriately wide variants of their layout.)
[Edit to add much later:
As of iOS 12 or so this trick has an interesting side effect: for 5 or fewer tabs, it turns the tab bar into iPad-style with the titles to the right of the icons.]
Make a CustomTabBarVC inherit it from UITabBarController and implement following it will make it possible for you.
import UIKit
class CustomTabBarVC: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
override var traitCollection: UITraitCollection {
let realTraits = super.traitCollection
let fakeTraits = UITraitCollection(horizontalSizeClass: .regular)
return UITraitCollection(traitsFrom: [realTraits, fakeTraits])
}
}
You may use custom tab bar from here: http://cocoacontrols.com/platforms/ios/controls/infinitabbar
Or even this one:
http://cocoacontrols.com/platforms/ios/controls/ciexpandabletabbar
Related
In iOS 14, the new split view controller handles the bar button items for you. That's cool! So in portrait we see this:
We're in portrait, so that button at the top left summons the "overlay" version of the Primary column.
And in landscape we see this:
We're in landscape, so that button at the top left hides or shows the "side by side" version of the Primary column.
Now I want to know how to control the presence of these buttons individually. I see that I can set presentsWithGesture to false to hide both buttons, but that's not what I want. My question is: how can I hide the second button (landscape) but not the first button (portrait)?
Set a delegate on the split view controller and implement this delegate method:
func splitViewController(_ svc: UISplitViewController, willChangeTo displayMode: UISplitViewController.DisplayMode) {
svc.presentsWithGesture = displayMode != .oneBesideSecondary
}
In SwiftUI, this solution works – tested on iOS 16!
extension UISplitViewController {
open override func viewDidLoad() {
preferredDisplayMode = UISplitViewController.DisplayMode.oneBesideSecondary
// remove sidebar button, make sidebar always appear !
presentsWithGesture = displayMode != .oneBesideSecondary
}
}
I've managed to hide the navbar itself, but I want it all gone - charge icon, connection bars, clock - absolutely everything.
I'm aware this may create a 'dead end' for my app, but that's ok for my purposes.
I read here that it's apparently not legal. Is that still true?
The app is not for distribution, so I'd still be interested to hear solutions even if Apple doesn't like it.
Thank you.
I assume you want to hide status bar.
You can use prefersStatusBarHidden property to hide status bar in Swift 4.2:
class ViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return hideStatusBar
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Is there a way how to set the default status bar style while keeping the UIViewControllerBasedStatusBarAppearance enabled?
Here is the problem, I'm dealing with:
Nearly the whole app needs to be using UIStatusBarStyle.LightContent as the navigation bar has a dark background. Originally, UIViewControllerBasedStatusBarAppearance was disabled and the following was set in in Info.plist for while text status status bar:
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
This worked just fine until I found out that this .LightContent status bar is shown even for some of the share extensions, like Facebook Messenger, causing it to be unreadable:
This could be solved by using UIViewControllerBasedStatusBarAppearance, but then I would need to add the following method to all of view controllers which I want to avoid as the app is quite large.
Moreover, for the one screen in the app that has light nav bar background, I was switching to dark nav bar using UIApplication.sharedApplication().setStatusBarStyle() but this method in deprecated in iOS 9.
Any ideas how to solve this? Swizzling?
Solution
The easiest and cleanest way how to achieve that is to add the following line in AppDelegate's application:willFinishLaunchingWithOptions method:
UINavigationBar.appearance().barStyle = .Black
This will make .LightContent as the default status bar style thorough the app as long as your app uses UINavigationController.
Don't forget to keep the following setting in app's Info.plist if want to use .LightContent status bar style during launch for splash screen:
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
TL;DR
My current setup, which is very similar to many other apps, uses UITabBarController as the top most controller with UINavigationController stack for each tab.
UINavigationController takes care of the status bar style (as it should) and do not call preferredStatusBarStyle() on its child view controllers. Therefore, implementing the subclassing solution proposed by par does not work in my case.
Further subclassing the custom subclass of UINavigationController I'm using would not be a clean solution either.
Now, since the app has UIViewControllerBasedStatusBarAppearance enabled and correct status bar style everywhere in the app itself, SFSafariViewController and share extension like Messages, Mail, etc use the correct (.Default) status bar style too.
The only exception where the correct status bar style is not used is the Facebook Messenger's share extension mentioned in the question. However, it seems to be a bug in the extension itself as all apps I have tried that use .LightContent status bar style (like Twitter, for example) have the same issue - presented FB Messenger share extension from the app has a status bar with white color text.
A solution I use quite frequently is to create a base view controller class that all view controllers in my app derive from. This has the advantage of allowing use of the view-controller-based status bar style-setting functionality with a default (light or dark) style, which can then be overridden on a per-view-controller basis as necessary.
A base view controller is also really handy once you start getting into trait-collection based changes, custom transition animations that you want for most view controllers, a central point for analytics tracking, and other useful things.
Yes, you have to go through your potentially large source base and change all your UIViewControllers into BaseViewControllers, but this is often as easy as a global search-and-replace.
Here's what the BaseViewController looks like with status-bar related methods:
class BaseViewController: UIViewController {
var statusBarHidden: Bool = false { didSet { setNeedsStatusBarAppearanceUpdate() } }
var statusBarStyle: UIStatusBarStyle = .lightContent { didSet { setNeedsStatusBarAppearanceUpdate() } }
var statusBarUpdateAnimation: UIStatusBarAnimation = .fade { didSet { setNeedsStatusBarAppearanceUpdate() } }
override var preferredStatusBarStyle: UIStatusBarStyle { return statusBarStyle }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { return statusBarUpdateAnimation }
override var prefersStatusBarHidden: Bool { return statusBarHidden }
}
For all view controllers that use the default light style, you don't need to do anything special:
class ViewController: BaseViewController { }
In the cases where you need a dark status bar, do:
class DarkStatusBarViewController: BaseViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
statusBarStyle = .default
}
}
Note also that you could rename the DarkStatusBarViewController above to DarkStatusBarBaseViewController and derive from it instead of BaseViewController when you need a dark status bar. Then you don't need to duplicate the status bar code in every view controller that needs it and you maintain a nice linear relationship for all your BaseViewController functionality.
There are many answers to the complementary question, which is how to prevent a transition to PrimaryOverLay on a from Regular to Compact interface change, eg use
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool
In my case, I have an iPhone 6+ with the detail view showing in portrait. When I rotate the device to horizontal (Compact to Regular), I want the primary view to stay hidden. I've tried setting the preferredDisplayMode to .PrimaryHidden in many places, but it has no apparent affect. Googling has turned up nothing.
Well, after I wrote the question, but before posting it, I tripped on a possible solution, which is to override the trait collection that the split view controller references.
I took that idea and decided to subclass UISplitViewController, and override the traitCollection property. That did the trick:
final class MySplitViewController: UISplitViewController {
var didOnce = false
override var traitCollection: UITraitCollection {
let old = super.traitCollection
let change = UITraitCollection(horizontalSizeClass: .Compact)
let new = UITraitCollection(traitsFromCollections: [old, change])
return new
}
Obviously this is hardcoded for one device - later I'll go and add some functions that I can use to control what is in fact returned.
Don't override traitCollection, instead use the method setOverrideTraitCollection:forChildViewController: in a parent view controller of your split controller, like in Apple's example AAPLTraitOverrideViewController.m
If your split controller doesn't have a parent, making a parent is really easy in the Storyboard. Add a new view controller, make it the entry point, add a container view, delete the default embedded view and instead add an embed segue to the split controller and set the override on self.childViewControllers.firstObject in viewDidLoad.
Looking to create a floating menu in Swift for an iOS application I am developing. Something along the lines of the little red circle menu as shown in the following image.
My initial thoughts were to extend the UIViewController class and add the respective drawing/logic there, however, the application is comprised of a few other controllers, more specifically the UITableViewController which in itself extends UIViewController. Is there perhaps a good place for an extension perhaps? Or is there a more eloquent way of drawing the menu on specific views without the mass duplication of menu related code?
The menu itself will be shown on most screens, so I need to selectively enable it. It'll also be somewhat contextual based on the view/screen the user is currently on.
Any awesome ideas?
You can create your own with the animations and all the things, or you can check this library
https://github.com/lourenco-marinho/ActionButton
var actionButton: ActionButton!
override func viewDidLoad() {
super.viewDidLoad()
let twitterImage = UIImage(named: "twitter_icon.png")!
let plusImage = UIImage(named: "googleplus_icon.png")!
let twitter = ActionButtonItem(title: "Twitter", image: twitterImage)
twitter.action = { item in println("Twitter...") }
let google = ActionButtonItem(title: "Google Plus", image: plusImage)
google.action = { item in println("Google Plus...") }
actionButton = ActionButton(attachedToView: self.view, items: [twitter, google])
actionButton.action = { button in button.toggleMenu() }
}
There is another alternative with this great library :
https://github.com/yoavlt/LiquidFloatingActionButton
You just have to implement the delegate and the dataSource in your ViewController:
let floatingActionButton = LiquidFloatingActionButton(frame: floatingFrame)
floatingActionButton.dataSource = self
floatingActionButton.delegate = self
You could use view controller containment. The menu can be its own view controller with its view laid transparently over top the content view controller.
For example this can be set up in the storyboard by dragging out two container views into a vanilla view controller.