Can't get Navigation bar customized after the first screen. - ios

My home screen looks exactly like I want it to but the next screen just has the back button up there despite having run the same code. Here is the code I am running in the initial view controllers view did load method.
let nav = self.navigationController?.navigationBar
nav?.barStyle = UIBarStyle.Black
nav?.tintColor = UIColor.yellowColor()
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height:40))
imageView.contentMode = .ScaleAspectFit
let image = UIImage(named: "NavBarPhoto")
imageView.image = image
navigationItem.titleView = imageView

the solution is to make one root view controller where you customize your navigation bar in the viewDidLoad.
Then all your view controllers should inherit for it.

When creating the folder for the ViewController Class that was giving me trouble I accidentally just pressed enter and let Xcode name the file, well, simply file. I quickly corrected the problem, thinking that it would have no other affect. Subsequently, while running my app I left getting Unknown class PickingViewController in Interface Builder file. I suspected that all these issues were tied together and they were. I clicked on the troublesome view, added him to the current module, now on to the next problem. Thanks guys for the comments.

Related

Swift: Adding floating button to TableViewController?

I'm having trouble at TableViewController.
I want to add floating button, but I found out that if I create tableview with TableviewController in Storyboard, then tableview is superview in that view controller, which means only way to add button is adding button in tableview as one of a cell, which is not floating button. (Maybe I'm wrong. I'm a bit confused. I can't add another view by Storyboard.)
I googled several times and I think the only solution is to add button by using UIWindow, but part of the solution codes are deprecated.
I hope I can get alternate solution for my problem.
Obviously the best solution is using UIViewController and adding UITableView and your button as subviews (as #Surjeet Singh suggested in comment). However if you face troubles doing this (maybe too complex right now), you can add UIButton as subview of your keyWindow as workaround. however keep in mind that you need to manually remove the button from keyWindow once your UITableViewController is going to disappear, or else your button will be appearing on other UIViewControllers. Here is the workaround solution:
func addFloatingButton() {
let keyWindow = UIApplication.shared.keyWindow
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
button.backgroundColor = .red
keyWindow?.addSubview(button)
}

What is a good way to add UIPageViewController to parent UIViewController without considering status bar height?

Currently, I have a UIViewController, with its top component consists of a horizontal UICollectionView (MenuTabsView.swift)
Now, I would like to add a UIPageViewController, just below the MenuTabsView.
I have tried the following few approaches.
Programatically without taking status bar height into consideration
func presentPageVCOnView() {
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY)
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
Here's the outcome.
From 1st glance, it seems that UIPageViewController's view need to offset by Y status bar distance. (But why?)
Programatically by taking status bar height into consideration
func presentPageVCOnView() {
let statusBarHeight = CGFloat(20.0)
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY + statusBarHeight, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY - statusBarHeight)
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
Now, it looks way better.
Use container view without status bar offset
But, I don't feel comfortable, on why we need to manually consider status bar height, during programatically way. I was thinking, maybe I can add a ContainerView to UIViewController, and "attach" the UIPageViewController's view to it?
(I am not sure why during adding Container View to storyboard, an additional UIViewController will be added along. Anyhow, I just manually delete the additional UIViewController)
Then, I use the following code to "attach" the UIPageViewController's view to new container view.
func presentPageVCOnView() {
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = containerView.frame
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
But, the outcome is not what as expected. Y offset still happen!!!
Use container view with status bar offset
I try to make sure, there are space of 20, between the top component MenuTabsViews and UIPageViewController's view.
I was wondering, is there any good practice/ solution, to ensure we can add UIPageViewController's view below another component, without affecting by status bar height?
You can do this all without any code -- it just takes an understanding of how UIContainerView works.
There's no real UIContainerView class... it is an automated way of adding a child view controller via Storyboard / Interface Builder. When you add a UIContainerView, IB automatically creates a "default" view controller connected to the container view with an Embed segue. You can change that default controller.
Here's step-by-step (images are large, so you'll probably want to click them to see the details)...
Start with a fresh UIViewController:
Add your "Menu Bar View" - I have it constrained Top/Leading/Trailing to safe-area, Height of 60:
Drag a UIContainerView onto the view - note that it creates a default view controller at the current size of the container view. Also note that it shows a segue. If you inspect that segue, you'll see it is an Embed segue:
Constrain the Top of the container view to the Bottom of your Menu Bar View, and Leading/Trailing/Bottom to safe-area. Notice that the size of the embedded view controller automatically takes the new size of the container view:
Select that default controller... and delete it:
Drag a new UIPageViewController onto your Storyboard and set its Custom Class to PageControllerVC:
Now, Ctrl-Click-Drag from the Container view to the newly added page view controller. When you release the mouse button, select Embed from the popup:
You now have an Embed segue from the container view to your page view controller. Notice that it automatically adjusted its size to match the container view size:
Since the Menu Bar View top is constrained to the safe-area, it will behave as expected.
Since the container view top is constrained to the bottom of the Menu Bar View, it will stay there, and should give you what you want.
No Code Needed :)
Edit
The most likely reason you ran into trouble with loading via code is with you frame setting.
If you try to set frames in viewDidLoad(), for example, auto-layout has not configured the rest of the view hierarchy... so framing will not be what you expect.
You're much better off using auto-layout / constraints, rather than setting explicit frames anyway.
Here is how I would do it from code (assumes you have your "Menu Bar View" connected via #IBOutlet):
class ViewController: UIViewController {
#IBOutlet var menuBarView: UIView!
var pageControllerVC: PageControllerVC?
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as? PageControllerVC else {
fatalError("Could not instantiate PageControllerVC!!!")
}
guard let v = vc.view else {
fatalError("loaded PageControllerVC had no view ????")
}
addChild(vc)
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: menuBarView.bottomAnchor),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor),
v.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
vc.didMove(toParent: self)
self.pageControllerVC = vc
}
}
You should remove safeArea pinning for pageVC.
Safe area includes status bar and iPhone 11+ top space
tabBar.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
// to this
tabBar.topAnchor.constraint(equalTo: self.view.topAnchor)
And in storyboards change Safe Area to SuperView

Problem: Background color of programmatic UI remains black

I've created the UI of a modal that pops up when you press a Tab Bar Item within a Tab Bar Controller. This is the first time that I've accomplished this programmatically, so there may be something that I'm missing here, but I can't seem to change the background color of the modal at all. More specifically, I'm trying to make the background transparent, but it appears in black when the modal is presented, no matter what color I change it to. I'm not sure that it matters that I've programmatically added subviews to the main view (for example, a UIView called "titleContainer"), but want to note this here just in case.
Below is part of what I have in my code:
override func loadView() {
view = UIView()
view.backgroundColor = UIColor.clear
view.isOpaque = false
titleContainer = UIView(frame: CGRect(x: 0, y: 0, width: 414, height: 180))
titleContainer.layer.borderWidth = 1.0
titleContainer.layer.borderColor = UIColor.darkGray.cgColor
titleContainer.layer.backgroundColor = UIColor.black.cgColor
titleContainer.layer.cornerRadius = 12
titleContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
titleContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleContainer)
NSLayoutConstraint.activate([
titleContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
titleContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
titleContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
titleContainer.heightAnchor.constraint(equalToConstant: 180),
])
}
UPDATE: In addition to the fact that the original code was adding a color to the layer of the UIView, the modal was also sub-classing UITabBarController and not UIViewController, hence why the background color of the modal seemed to be inheriting that of the first VC in the Tab Bar Controller. Changing the superclass to UIViewController ultimately resolved this issue.
You're adding a color to the layer of the UIView. Remove the following line to get the view's background color. Or you can update the layer's color instead.
titleContainer.layer.backgroundColor = UIColor.black.cgColor
To set the color of the previous ViewController's view background you could set it while transitioning as follows:
viewController.titleContainer.layer.backgroundColor = view.backgroundColor?.cgColor
So your solution is correct the subclassing of your modal controller. In the future, you may want to double check the parent class of your subclasses especially when something off is happening like in your case.
And secondly, another way to help you debug the application is to toggle the attributes of your controller, say. a background color, and if you change your first tab screen's bg color, and the modal copies the color, then you can get to a theory that your modal is somehow getting that attribute from the first tab screen.
Lastly, utilize the Xcode's debugging features. One of those is Debug View Hierarchy. - This will give you the hierarchies of your views in 3-dimensional perspective.
Make sure to select overCurrentContext as modalPresentation for the vc
vc.modalPresentation = overCurrentContext
// present the vc
vc.bColor = UIColor.red
and declare this inside the vc
var bColor:UIColor!
Then set it here
titleContainer.layer.backgroundColor = bColor
Make you super view color to clear for your presenter view controller
self.view.backgroundColor = .clear
Than write this where ever you want to present your view controller
let controllerObject = yourControllor object from the storyboard
controllerObject.modalPresentationStyle = .overFullScreen
controllerObject.modalTransitionStyle = .coverVertical
present(controllerOject, animated: true, completion: nil)

Swift Nav Bar with Title and Page Control

I am developing a swift app but having some problem on putting title and page control, together on the nav bar. I only manage to put either one of them on the nav bar, but not both. I am hoping for something like this(as well as the animation when swiping to another view controller) :
Below is my screen output. Page controller(embedded in Navigation controller) is working, just wanted to add titles(change base on view) and page control.
And here's how I create the title:
This is how I create the title:
#IBOutlet weak var navBar: UINavigationItem!
let title: UILabel = UILabel(frame: CGRectMake(0, 0, 150, 44))
title.numberOfLines = 2
title.textAlignment = .Center
title.text = "News\n"
navBar.titleView = title
I am making the title into 2 lines to leave room for the page control. However, when I try to create page control programmatically, it appears to be "behind" the nav bar.
This is how create page control:
let pageControl : UIPageControl = UIPageControl(frame: CGRectMake(0, 0, 150, 44))
self.pageControl.numberOfPages = 3
self.pageControl.currentPage = 0
self.view.addSubview(pageControl)
Add both your label and page control to another view, setting the constraints / frames appropriately, then add that view as the titleView for the nav bar.

Creating nav bar title image for select view controllers produces weird behavior

I am created a logo in nav bar title spot on select view controllers using the below function:
class func setUpLogoOnNavBar(sourceVC: UIViewController) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 135, height: 90))
imageView.contentMode = .ScaleAspectFit
let image = UIImage(named: "LogoAppHeaderBar.png")
imageView.image = image
sourceVC.navigationItem.titleView = imageView
})
}
By putting this on the main thread using GCD, it causes the image to jump to the left of nav bar when I am performing a standard push segue, it looks really bad. When I remove the GCD code from the below func, the logo jumping goes away. However, I get This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. Also the logo doesn't load in some view controllers because (I assume) it's not performing this operation on the main thread. How do I handle this so that this image loads on the main thread, but doesn't jump when I segue? Thanks in advance!
Why are you using GCD at all when laying out your UI? It doesn't look like you are getting the image over the web, so laying out your nav bar should not be done asynchronously. Are you calling setUpLogoOnNavBar from another thread? You shouldn't.

Resources