Subviews in presented ViewController gets wrong position after rotation - ios

I'm experimenting with UISearchController, but I can't get it right. I present a clean UISearchController from my own UIViewController, and it looks great - but as soon as I rotate the device, it gets shifted a few points up or down.
To recreate this, just do these few steps:
Create a new Single View project
Delete Main.storyboard from the project files and remove its name from the project settings (Project -> General -> Target -> Main Interface)
In AppDelegate.swift:
application didFinishLaunchingWithOptions...{
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
ViewController.swift:
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 20, y: 200, width: 100, height: 40))
button.setTitle("Search", for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(click), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func click(){
self.present(UISearchController(searchResultsController: nil), animated: true, completion: nil)
}
}
And that's it. This is what I'm seeing:
Presenting the search when the device is in portrait mode looks great in portrait - but if you rotate the device to landscape while presenting the searchbar, it will be wrongly positioned, a few pixels above the top of the screen.
Presenting the search when in landscape will yield the opposite. It looks great in landscape, but when rotating it to portrait the entire search controller view will be pushed down a few pixels.
It's not a matter of height size on the bar. The entire bar gets pushed up/down.
I tried investigating a bit further. I presented the search controller from landscape mode and rotated to portrait, and then debugged the view hierarchy:
To be honest, I'm not quite sure what I'm looking at. The top-most view is a UISearchBarBackground embedded within a _UISearchBarContainerView, which is within a _UISearchControllerView.
As you can see in the size inspector on the right side, the middle-view "container" has y: 5, which makes no sense. When debugging the correct state, y is 0. What's really interesting is that the top-most view has y: -44 in this corrupt situation, and it has the same when it's correct - but you can clearly see that there is some space leftover above it. There seem to be three different y-positions. I don't get it.
I've read some guides on how to implement a UISearchController, but literally every single example I find is people modally presenting a custom ViewController that contains a SearchController. This will result in the entire custom ViewController being animated up from below.
Since the UISearchController is a subclass of UIViewController, I wanted to test out presenting it directly, not as part of a regular UIViewController. This gives the cool effect that the searchBar animates in from above, and the keyboard from below.
But why doesn't this work?
Edit: I just found out that if I enable Hide status bar in the project settings, the UISearchController looks even more correct in landscape than the "correct state" from above, and even animates correctly to portrait. It's super weird, because the status bar doesn't change at all. It was never visible in landscape. Why does this happen? It seems so buggy. Look at these three states:
The first state is when showing search controller from portrait then rotating to landscape (doesn't matter if Hide status bar is enabled or not.
The second state is when showing search controller from landscape if Hide status bar is false
The third state is when showing search controller from landscape if Hide status bar is true.

As stated in the documentation:
Although a UISearchController object is a view controller, you should
never present it directly from your interface. If you want to present
the search results interface explicitly, wrap your search controller
in a UISearchContainerViewController object and present that object
instead.
Try to wrap your UISearchController inside a UISearchContainerViewController.

How about make a custom view contains search bar.
Make it on top of the layers.
And when rotate occurs update frame/constraints of it.

This is covered in guidelines for designing for iPhone X. Now Navigation bar contains search controller. You can instanciate the search controller and set it to navigationItem.searchController. Now it will handle the search controller while rotation.
var search = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = search

You can do this tric
class ViewController: UIViewController {
let sc = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 20, y: 200, width: 100, height: 40))
button.setTitle("Search", for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(click), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func click(){
self.present(sc, animated: true, completion: nil)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
var mFrame = sc.view.frame
mFrame.origin.y = 5.0
sc.view.frame = mFrame
}

Related

Tab bar item icons are sizing very weird

So I don't know how this is happening at all. Ive tried everything remotely related to what might be happening, with no success. I have an initial view controller that presents a tab bar controller onto the screen. And any way I set the icons, which are all the same sizes 1* 2* and 3* wise, the right bar button is abnormally bigger than the other two!?
Heres what it looks like:
class SceneScrollViewController: UITabBarController, UIScrollViewDelegate, UITabBarControllerDelegate {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
delegate = self
let exploreVC = Storyboard.explore.instantiateInitialViewController() as? UINavigationController
let profileVC = Storyboard.profile.instantiateInitialViewController() as? UINavigationController
let hubVC = Storyboard.hub.instantiateInitialViewController() as? UINavigationController
profileVC.tabBarItem.image = UIImage.init(named: "profile")
hubVC.tabBarItem.image = UIImage.init(named: "homeIcon")
exploreVC.tabBarItem.image = UIImage.init(named: "tabBarSearch")
viewControllers = [exploreVC, hubVC, profileVC]
self.selectedIndex = 1
}
}
If you have any idea what is happening please post them, anything helps. Also, the images are all proper size, and what's weirder: if I swap the search icon with profile ie, that same right buttonItem enlarges the search icon, and the profile icon sizes to the current search icon size below. SO no matter what image I set in right button slot, it sizes it weird.
Double-check your icon size, if still showing a larger icon then try to add insets to the tabBarItem.
profileVC.tabBarItem.imageInsets = UIEdgeInsets(top: -4, left: -4, bottom: -4, right: -4)
to make the size of all tabbar icons consistent you can give imageInsets to all tabs.

Swift Presenting Controller Dismiss Bar Indicator

This should be an easy question for most of you. Presenting view controllers like in the attached photo now have a bar at the top of them (see red arrow) to indicate that the user can swipe down to dismiss the controller. Please help with any of the following questions:
What is the proper term for this icon?
Is it part of swift's ui tools / library or is it just a UIImage?
Can someone provide a simple snippet on how to implement - perhaps it is something similar to the code below
let sampleController = SampleController()
sampleController.POSSIBLE_OPTION_TO_SHOW_BAR_ICON = true
present(sampleController, animated: true, completion: nil)
Please see the red arrow for the icon that I am referring to
grabber
grabber is a small horizontal indicator that can appear at the top edge of a
sheet.
In general, include a grabber in a resizable sheet. A grabber shows people that they can drag the sheet to resize it; they can also
tap it to cycle through the detents. In addition to providing a visual
indicator of resizability, a grabber also works with VoiceOver so
people can resize the sheet without seeing the screen. For developer
guidance, see prefersGrabberVisible.
https://developer.apple.com/design/human-interface-guidelines/ios/views/sheets/
From iOS 15+ UISheetPresentationController has property prefersGrabberVisible
https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller/3801906-prefersgrabbervisible
A grabber is a visual affordance that indicates that a sheet is
resizable. Showing a grabber may be useful when it isn't apparent that
a sheet can resize or when the sheet can't dismiss interactively.
Set this value to true for the system to draw a grabber in the
standard system-defined location. The system automatically hides the
grabber at appropriate times, like when the sheet is full screen in a
compact-height size class or when another sheet presents on top of it.
Playground snippet for iOS 15:
import UIKit
import PlaygroundSupport
let viewController = UIViewController()
viewController.view.frame = CGRect(x: 0, y: 0, width: 380, height: 800)
viewController.view.backgroundColor = .white
PlaygroundPage.current.liveView = viewController.view
PlaygroundPage.current.needsIndefiniteExecution = true
let button = UIButton(primaryAction: UIAction { _ in showModal() })
button.setTitle("Show page sheet", for: .normal)
viewController.view.addSubview(button)
button.frame = CGRect(x: 90, y: 100, width: 200, height: 44)
func showModal {
let viewControllerToPresent = UIViewController()
viewControllerToPresent.view.backgroundColor = .blue.withAlphaComponent(0.5)
viewControllerToPresent.modalPresentationStyle = .pageSheet // or .formSheet
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
}
viewController.present(viewControllerToPresent, animated: true, completion: nil)
}
The feature you are asking is not available in UIKit.
You have to implement custom view-controller transition animation with subclassing UIPresentationController for rendering that pull up/down handle.
UIPresentationController (developer.apple.com)
For custom presentations, you can provide your own presentation controller to give the presented view controller a custom appearance. Presentation controllers manage any custom chrome that is separate from the view controller and its contents. For example, a dimming view placed behind the view controller’s view would be managed by a presentation controller. Apple Documentation
This can be achieved by any UIView or you can use any image if you want by adding subview to UIPresentationController's contentView above the presentedView.
To provide the swipe gesture to dismiss/present, you must implement UIPercentDrivenInteractionController.
You can refer to this tutorial below for detailed understanding.
UIPresentationController Tutorial By raywenderlich.com
You should look for presentationDirection = .bottom in your case.
For gesture driven dismissal, you should check below tutorial
Custom-UIViewcontroller-Transitions-getting-started
I hope this might help you.
If you need to add this indicator within the view controller that is being presented if you do not want to do any custom presentations and just work with the default transitions.
The first thing to think about is your view hierarchy, is the indicator going to be part of your navigation bar or perhaps your view does not have navigation bar - so accordingly you probably need some code to find the correct view to add this indicator to.
In my scenario, I needed a navigation bar so my view controllers were within a navigation controller but you could do the same inside your view controllers directly:
1: Subclass a Navigation Controller
This is optional but it would be nice to abstract away all of this customization into the navigation controller.
I do a check to see if the NavigationController is being presented. This might not be the best way to check but since this is not part of the question, refer to these answers to check if a view controller was presented modally or not
class CustomNavigationController: UINavigationController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this checks if the ViewController is being presented
if presentingViewController != nil {
addModalIndicator()
}
}
private func addModalIndicator() {
let indicator = UIView()
indicator.backgroundColor = .tertiaryLabel
let indicatorSize = CGSize(width: 30, height: 5)
let indicatorX = (navigationBar.frame.width - indicatorSize.width) / CGFloat(2)
indicator.frame = CGRect(origin: CGPoint(x: indicatorX, y: 8), size: indicatorSize)
indicator.layer.cornerRadius = indicatorSize.height / CGFloat(2.0)
navigationBar.addSubview(indicator)
}
}
2: Present the Custom Navigation Controller
let someVC = UIViewController()
let customNavigationController = CustomNavigationController()
customNavigationController.setViewControllers([stationsVC], animated: false)
present(playerNavigationController, animated: true) { }
3: This will produce the following results
You might need to alter some logic here based on your scenario / view controller hierarchy but hopefully this gives you some ideas.

iOS 11 prefersLargeTitles not updating until scroll

I implemented a basic UIViewController with a UITableView that's wrapped in a UINavigationController. I set prefersLargeTitles to true:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.title = "Coffees"
}
However, the title stays small until I scroll the view, at which point it enlarges. I tried moving that call to where I create the UINavigationController, but there was no effect. I am sure the navigationController is not nil when I set prefersLargeTitles.
Should I be updating that property elsewhere? Or should I file a Radar?
Update:
This only seems to happen if my view contains a UITableView or is itself a UITableViewController
I recently hit the same issue and none of the suggestions worked for me. Instead, all I needed to do was to invoke sizeToFit(). Sample code:
private func configureNavigator() {
guard let navigationController = navigationController else { return }
navigationController.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .automatic
navigationController.navigationBar.sizeToFit()
}
I hope this helps!
For me the only working solution is:
DispatchQueue.main.async { [weak self] in
self?.navigationController?.navigationBar.sizeToFit()
}
in
viewWillAppear()
I had the same issue only on one tableview ...
I had to set :
self.tableView.contentInsetAdjustmentBehavior = .never
so that my tableview stop scrolling when uiviewcontroller was loaded.
It's the tableview automatic scrolling that makes the large title being hidden
Hope this helps
I had the same problem. Although you are not using Storyboards but I hope this could help someone. I checked "Prefer Large Titles" for the Navigation Controller (not the View Controller) I embedded my TableViewController in. All the View Controllers after the Navigation Controller turned and had large titles, and it should work.
Same issue with Swift 5.2
my view contains tableView and prefersLargeTitles is not updating until scroll, I fixed it by setting
self.tableView.contentInsetAdjustmentBehavior = .never
Modifying the contentInset of the tableView with top:1 will force the NavigationBar to expand and display the large titles.
Obj-C
-(void) viewWillAppear:(BOOL)animated {
if (#available(iOS 11.0, *)) {
tableView.contentInset = UIEdgeInsetsMake(1, 0, 0, 0);
}
}
Swift
override func viewWillAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
tableView.contentInset = UIEdgeInsetsMake(1, 0, 0, 0)
}
}
Note: If you have a tableView.reloadData() in your viewWillAppear make sure to call it after editing the contentInset
In my case the solution was to set tableView's top align to Safe Area and not Superview
I ran into the same issue and found that it’s usually best to set the prefersLargeTitles property from the view controller or object that sets it up, and to do so before it is presented.
For instance, if the view controller in question is shown upon app launch:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
let someViewController: UIViewController = CustomViewController()
let theNavController = UINavigationController(rootViewController: someViewController)
theNavController.navigationBar.prefersLargeTitles = true
window.rootViewController = theNavController
window.makeKeyAndVisible()
return true
}
or if presenting a particular view controller:
let someViewController: UIViewController = CustomViewController()
let theNavController = UINavigationController(rootViewController: someViewController)
theNavController.navigationBar.prefersLargeTitles = true
present(theNavController, animated: true, completion: nil)
I found this method to be a more sure-fire way to ensure that the navigation title is displayed accordingly. Hope this helps! :)
I have wasted some considerable amount of time on this as prefersLargeTitle saga works on some view controllers as expected and with some it produces the same issue above.
Solution for me was to uncheck Extended Edges Under Top Bars in IB - for those view controllers who show large title momentarily until the contents of the table view are loaded then navigation bar jumps back up to regular size. It only shows the large title when scrolling the table view down.
This is backward compatible with iOS 10 and does not leave any empty space above the first row in the table view.
I had checked prefersLargeTitle on the navigation controllers attributes inspector only in IB - nothing in code. Same for largeTitleDisplayMode = .always
As for why this happens with some view controllers and not others, I have absolutely no idea!
In the storyboard I set the Navigation Item's Large Title to Never.
In my ViewController's viewDidLoad method I set the following:
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
Programmatically:
In AppDelegate.swift:
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let navigationController = UINavigationController.init(rootViewController: ViewController())
window?.rootViewController = navigationController
In ViewController:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .automatic
}
override func loadView() {
super.loadView()
view.addSubview(tableView)
view.addSubview(loadingView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor),
tableView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor)
])
}
Make sure your tableView has beed previously added to your view.
I tried all of the other answers but what worked for me was to set the content offset of my UITableView to CGPoint(x: 0, y: -1) in viewDidLoad:
tableView.setContentOffset(CGPoint(x: 0, y: -1), animated: false)
I just had this same issue and, in my case, it turns out that the Storyboard structure that was working in iOS 10 with Swift 3 (and also works with iOS 11 with Swift 3) was causing the issue on iOS 11 with Swift 4.
To elaborate:
I had a regular UIViewController in my storyboard that I had set to a UINavigationController subclass (my hierarchy is similar to yours, with UITabBarController subclass → UINavigationController subclass → UITableViewController subclass).
In iOS 10, this worked fine.
In iOS 11, this also works fine when you run the existing Swift 3 app.
However, with the Swift 4 app, running on iOS 11, I was seeing the same symptoms you described (large titles only appear when you pull/scroll the view down).
To fix, I replaced the UIViewController-based elements in the Storyboard with actual UINavigationController instances (which contain a UINavigationBar explicitly in the Storyboard – I have a hunch this is where the crux of the issue stems from, as the UIViewController instances didn’t have that element explicitly declared within the Storyboard).
Anyway, that fixed the issue for me.
I’ll file radar as this looks like a Swift 4-based regression as, for me, it works both in iOS 10 with Swift 3 and in iOS 11 with Swift 3.
General changing the behaviour of the navigationBar should be done in viewWillAppear(_:)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.prefersLargeTitles = true
}
After doing that it worked fine for me.
One more possible solution is to end refresh in your refreshHandler(). like this-
#objc func refreshPage() {
self.refreshControl?.endRefreshing() //End here
self.loadTableData() //Get fresh data and reload table
}
I solved this issue via storyboard
Navigation Controller -> Navigation Bar -> Attributes inspector -> Prefers Large Titles(Checked)
View Controller -> Navigation Item -> Attributes inspector -> Large Title (Automatic or Always checked)
I think It does seem a little bit dummy but I effectively solved the problem with this:
self.navigationItem.prompt = ""
self.navigationItem.prompt = nil
It's like navigationBar needs a sort of update in one of its elements to update the layout.
Sometimes to update something in navigationBar I need to hide and unhide it.. That's why I think there is a best way to do it.. For the moment that's my workaround.
I had a similar issue with navigation bar, but in my case it had a custom title view, and navigation bar remained empty until table view is scrolled down, which triggered UILayoutContainerView to layout its subviews, one of which are navigation controller's view and navigation bar. I assume the root of it is the same as the large title navigation bar issue.
Anchoring tableView to the safeAreaLayoutGuide didn't work out for me, largeTitleDisplayMode couldn't be other then .never
So I managed to fix it by calling self.navigationController?.view.setNeedsUpdateConstraints in the top presented controller's viewDidAppear(animated:) function, or scheduling this call for the next run loop in viewWillAppear(animated:), like:
DispatchQueue.main.async {
self.navigationController?.view.setNeedsUpdateConstraints()
}
In this case, navigation bar appeared with the correct content and size along with presenting transition, instead of popping in after transition was completed
What worked for me is setting the self.navigationController?.navigationBar.prefersLargeTitles = true before calling tableviews reload method.
The answers above for tableview also works for collection view if parent view not scrolled before navigation:
self.collectionView.contentInsetAdjustmentBehavior = .never
If parent view scrolled before navigation the solution above did not work in my case. I had to add the answer above as:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.sizeToFit()
}
That does seem like a weird behavior at first, but try setting the navigationItem.largeTitleDisplayMode to always. The default is automatic - and it's not defined how that works in the docs.
Also wrote / will update an answer about large titles here.
I had the similar issue. The view is a table view. The property of prefersLargeTitles is set at viewDidLoad event. Then I set view's title in viewWillAppear event.
override open func viewDidLoad() {
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.prefersLargeTitles = true
} else {
// Fallback on earlier versions
}
...
}
override open func viewWillAppear(_ animated: Bool) {
self.navigationItem.title = "something"
...
}
In my prepare segue event, I set navigation item's tile to nil so that the next view left navigation var item displays "Back" automatically.
override func prepare(for segue: UIStoryboardSegue,
sender: Any?) {
self.navigationItem.title = nil
...
}
The first time the table view displays large title correctly. However, if I select a row to next view and back to the table view, the navigation item's title becomes empty.
After several hours' struggling, I finally find out that the view's title should be set in viewDidAppear event! It seems that whatever set to view's title in Will event would be reset by UIKit internally back to nil. So it has to be set in a different event.
override func viewDidAppear(_ animated: Bool) {
self.navigationItem.title = "something"
...
}
override open func viewWillAppear(_ animated: Bool) {
// self.navigationItem.title = "something" // Remove it and set title in Did event!
...
}
Before I introduced this iOS 11 new feature, my app runs OK. It seems that the new feature has some changes in UIKit so that the previous version app may need some updates/changes to make it work.
I had the same issue and fixed it by changing order of views in my ViewController in InterfaceBuilder.
It seems like if the first view in Hierarchy is NOT a ScrollView then NavigationBar appears in LargeTitle mode and does not animates together with scroll view. If you need to have Navigation Bar title to reflect your scroll then you need to put your scroll view as the first in view hierarchy.
Also, I am not completely sure in this but looks like Navigation Bar appearance in standard or Large Title mode depends on views hierarch of previous Controller.
Similar issue for me with a UITableViewController added to a UIViewController. In my instance, these view controllers are themselves embedded in a UITabBarController and only the first tab displayed correctly used a large title. Other tabs required a manual scroll before the large title was displayed.
The only thing I could get to work was adjusting the contentInset as per #pau-senabre's answer, except I the top inset wasn't helpful for me. Instead, I set the left inset and then reset it on the next runloop.
private var isFirstAppearance = true
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFirstAppearance {
applyLargeTitlesFix()
}
}
private func applyLargeTitlesFix() {
let originalInset = tableViewController.tableView.contentInset
tableViewController.tableView.contentInset = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 0)
DispatchQueue.main.async { [weak self] in
self?.tableViewController.tableView.contentInset = originalInset
}
isFirstAppearance = false
}
Same issue here with Swift 4.2, iOS 12, and refactored Storyboards.
Tried adding prefersLargeTitles = true to viewWillAppear and viewDidLoad, but neither fixed my issue.
Instead, I copied the refactored storyboards back into main.storyboard and found the option to enable large titles in IB. Set that option, then refactored the storyboards back out and everything is working now. For some reason, the initial refactoring stripped out the option and I couldn't enable it programmatically.
I had the same issue (iOS 14, Xcode 12.2).
It only affected navigation controllers displaying table views.
I had originally set tableView.tableFooterView = UIView() to get rid of extra separators after the last cell. Setting the footer view to nil fixed the scrolled-up navigation title.
I had same issue.
I have set below code in viewdidload method. and it get fixed.
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .never

TabBar to view and when closed come back to previous selected view

I'm developing an iOS app with swift in which I have a TabBarController with 5 tab bar items. All of them points to a navigation controller and then to a view controller. One of them I want to show a view controller without the tab bar and when the user press cancel it should go back to the previous tab bar item/view that was selected (previously - sorry for the redundancy). They are all linked/referenced by a "Relationship "view controllers" to "name of the view", but I don't have any specific segue or whatsoever.
This is the code for that specific "button" which I call in the viewDidLoad function:
func setupMiddleButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.white
menuButton.layer.cornerRadius = menuButtonFrame.height/2
menuButton.setImage(UIImage(named: "klein_fototoestel_2"), for: UIControlState.normal) // 450 x 450px
menuButton.contentMode = .scaleAspectFit
menuButton.addTarget(self, action: #selector(menuButtonAction), for: UIControlEvents.touchUpInside)
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
func menuButtonAction(sender: UIButton) {
self.selectedIndex = 2
}
I tried to perform the behaviour I want by delegating the tab bar controller with the following code but this function is never called when the central button is selected (though the correct view shows up..!):
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print("the selected index is : \(tabBar.items?.index(of: item))")
}
What I really want to know is what is the correct way to implement that behaviour I want. Remembering that all views have a navigationController before. I read a lot of people suggesting using UserDefaults to store the index of the previous controller but to be honest I really don't think that's appropriate.
Any help is appreciated.
Thanks in advance.
I think you were on the right track - just need to get the correct connections.
In this diagram (it's kinda big - easier to read if you open it in a new tab), you see a "standard" UITabBar structure. The key is putting a default "do-nothing" view controller as the 3rd tab, and then adding a "special" view controller which will be loaded via code:
Then, your "action" function will look something like this:
func menuButtonAction(sender: UIButton) {
// Don't navigate to the tab index
//self.selectedIndex = 2
// instead, load and present the view you really want to see
if let vc = storyboard?.instantiateViewController(withIdentifier: "SpecialVC") as? SpecialViewController {
vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(vc, animated: true, completion: nil)
}
}
You can see and download a working example here: https://github.com/DonMag/SWTabsWithSpecialTab

Status Bar White Opaque on Launch

My initial view controller is a navigation controller and its root view controller is a UIViewController which conforms to the UIPageViewControllerDataSource protocol. The content for my pages are three unique view controllers which have both a scene in the storyboard and a swift file.
I am hiding the navigation bar in the root view controller's viewDidLoad().
self.navigationController?.navigationBar.hidden = true
When the app launches, the status bar is white and opaque. As soon as I scroll to the next page, it becomes translucent and the background color of the page shows through.
Appearance on Launch
Appearance While Swiping to Next Page
Can someone please help me understand what is happening here and how to fix it? In case it is not obvious, I want the second behavior from launch; the status bar is translucent and the page background shows through.
I have tried
In viewDidLoad(): UIApplication.sharedApplication().statusBarStyle = .LightContent
In the root view controller:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .Default
}
Followed by self.setNeedsStatusBarAppearanceUpdate() in viewDidLoad()
In the root view controller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.navigationController?.navigationBar.barStyle = .Default
self.navigationController?.navigationBar.barTintColor = UIColor.clearColor()
}
In viewDidLoad() (above navigationBar.hidden):
self.navigationController?.navigationBar.barStyle = .Default
self.navigationController?.navigationBar.barTintColor = UIColor.clearColor()
As a note, when I remove the navigation controller and just make the root view controller the initial view controller, the status bar appears as expected- translucent.
This question is similar, but none of the solutions worked, it is over a year old, and when I contacted the poster, he said that he thought he put a view underneath where the status bar would be. I'm not sure that I can manage the the view in such a way that it works seamlessly with the scroll aspect of the page view controller.
I found a solution. A friend of mine made a suggestion which led me to start thinking about the frame of the UIPageViewController. This led me to grab the height of the status bar and then adjust the frame down by that much.
In viewDidLoad:
let pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
// More pageViewController setup
let statusBarHeight: CGFloat = UIApplication.sharedApplication().statusBarFrame.height
let x = pageViewController.view.bounds.origin.x
let y = pageViewController.view.bounds.origin.y - statusBarHeight
let width = pageViewController.view.bounds.width
let height = pageViewController.view.bounds.height + statusBarHeight
let frame = CGRect(x: x, y: y, width: width, height: height)
pageViewController.view.frame = frame
I still do not understand why this needs to be manually adjusted. (Hopefully someone else will come along with a solution/explanation.) But I hope this bit of code helps someone.

Resources