I'm trying to learn how to programmatically create the UI of my application.
I added to the application a navigation controller with code, this is the code:
AppDelegate:
var window: UIWindow?
var navigationController: UINavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
if let window = window{
let mainVC = MainVC()
navigationController = UINavigationController(rootViewController: mainVC)
window.rootViewController = navigationController
window.makeKeyAndVisible()
}
return true
}
And this is my mainVC:
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.barTintColor = UIColor.blue
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.title = "Hello"
setupTextBox()
// Do any additional setup after loading the view, typically from a nib.
}
let textBox: UITextField = {
var tBox = UITextField(frame: CGRect(x: 100, y: 100, width: 200, height: 30))
tBox.placeholder = "Please Enter Name"
tBox.textAlignment = .center
tBox.borderStyle = .roundedRect
tBox.backgroundColor = UIColor.red
return tBox
}()
private func setupTextBox(){
view.addSubview(textBox)
}
}
The navigation bar does appear but it is huge, couldn't find a way to view it as it should be.
This is the image of the navigation bar:
What am I doing wrong?
Its because of navigationController?.navigationBar.prefersLargeTitles = true
You are doing well
Just must to understand that it is iPhone X with top safe area + LARGE TITLE
You're not doing anything wrong. That's simply how a UINavigationBar looks on iPhone X, iPhone XR and iPhone XS. All of these devices have the "X" bevel.
Here's another helpful SO post: What is the top bar height of iPhone X?
The large navigation bar is a style of navigation bar that is used when prefersLargeTitles is set. So this line is causing your issue
navigationController?.navigationBar.prefersLargeTitles = true
Change that to false and you should get a smaller* navigation bar
Note: * it will still be larger than normal due to the 'notch' on the iPhone X range of devices
Related
I was trying to navigate view controllers in a programmatic approach in swift. So, my purpose in this test is
Create two view controllers.
Navigate between view controllers when a button is pressed
So this is my AppDelegate.swift. I created Navigation controller and embedded ViewController.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = UINavigationController();
let mainViewController = ViewController();
let loginViewController = LoginViewController();
navigationController.viewControllers = [ mainViewController, loginViewController ];
self.window?.rootViewController = navigationController;
self.window?.makeKeyAndVisible()
return true
}
Then in View Controller
let button: UIButton = {
let button = UIButton(frame: CGRect(x: 120, y: 120, width: 50, height: 50));
button.setTitle("Login", for: .normal);
button.layer.borderColor = UIColor.lightGray.cgColor;
button.translatesAutoresizingMaskIntoConstraints = false;
button.backgroundColor = UIColor.red;
button.layer.cornerRadius = 5;
return button;
}();
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white;
view.addSubview(button);
button.addTarget(self, action: #selector(loginPressed(_sender:)), for: .touchUpInside);
// Do any additional setup after loading the view.
}
#IBAction func loginPressed(_sender: UIButton) {
print("clicked");
let loginViewController = LoginViewController();
self.navigationController?.pushViewController(loginViewController, animated: true);
}
And in LoginViewController
let emailLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 150, width: 150, height: 150));
label.text = "Email address";
return label;
}();
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(emailLabel);
print("this is loginViewController");
}
Here's my result. in ViewController
And in LoginViewController .
So here's my question: Why the button is showing up in the loginViewController? Why the uielement has been shared between view controllers ?
NOTE: I tried searching in google. Maybe because I am a web developer, I might not have searched with the right words.
Running the exact code you've posted, the initial view will be your LoginViewController (not your "Main" view controller) and the screen will be black.
So, to fix what you have there...
In AppDelegate don't try to create the LoginViewController - create and set only your "main" VC:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = UINavigationController();
let mainViewController = ViewController();
// don't try to create login view here...
//let loginViewController = LoginViewController();
//navigationController.viewControllers = [ mainViewController, loginViewController ];
// set only your first controller
navigationController.viewControllers = [ mainViewController ];
self.window?.rootViewController = navigationController;
self.window?.makeKeyAndVisible()
return true
}
Now, the initial view when you run the app will show your "main" VC with the Login button... however, tapping that button will push to a black screen, because you didn't give LoginViewController a background color:
class LoginViewController: UIViewController {
let emailLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 150, width: 150, height: 150));
label.text = "Email address";
return label;
}();
override func viewDidLoad() {
super.viewDidLoad()
// set background to green, to make it really obvious
view.backgroundColor = .green
view.addSubview(emailLabel);
print("this is loginViewController");
}
}
If you still see the Login button along with the email label, and / or the background is not green, I suspect you started by laying out your view controllers in Storyboard but did not clear the Main Interface in project settings (General tab):
If the Main Interface box is not blank, delete what's there and run your app again (with the above changes).
EDIT
Here is a complete example, using your code with the edits I described.
Create a new Project
Delete Main.storyboard
Copy and Paste the AppDelegate code below into AppDelegate.swift
Copy and Paste the ViewController code below into ViewController.swift
Delete the word Main from Main Interface in project settings / General
Run the app
See if you get what you want.
AppDelegate
//
// AppDelegate.swift
// NewProject
//
// Created by Don Mag on 8/6/19.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = UINavigationController();
let mainViewController = ViewController();
// don't try to create login view here...
//let loginViewController = LoginViewController();
//navigationController.viewControllers = [ mainViewController, loginViewController ];
// set only your first controller
navigationController.viewControllers = [ mainViewController ];
self.window?.rootViewController = navigationController;
self.window?.makeKeyAndVisible()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
}
ViewController
//
// ViewController.swift
// NewProject
//
// Created by Don Mag on 8/7/19.
//
import UIKit
class ViewController: UIViewController {
let button: UIButton = {
let button = UIButton(frame: CGRect(x: 120, y: 120, width: 50, height: 50));
button.setTitle("Login", for: .normal);
button.layer.borderColor = UIColor.lightGray.cgColor;
button.translatesAutoresizingMaskIntoConstraints = false;
button.backgroundColor = UIColor.red;
button.layer.cornerRadius = 5;
return button;
}();
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white;
view.addSubview(button);
button.addTarget(self, action: #selector(loginPressed(_sender:)), for: .touchUpInside);
// Do any additional setup after loading the view.
}
#IBAction func loginPressed(_sender: UIButton) {
print("clicked");
let loginViewController = LoginViewController();
self.navigationController?.pushViewController(loginViewController, animated: true);
}
}
class LoginViewController: UIViewController {
let emailLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 150, width: 150, height: 150));
label.text = "Email address";
return label;
}();
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
view.addSubview(emailLabel);
print("this is loginViewController");
}
}
First try to debug with backgroundColor and check if maybe the view is transparent.
Second remove the loginViewController from navigationController.viewControllers = [ mainViewController, loginViewController ]; because with this you are pushing an UIViewController to the stack.
You are creating a new instance of LoginViewController anyways when you push with let loginViewController = LoginViewController();
When you print your navigationController.viewControllers after the push you will have something like [mainViewController, loginViewController, loginViewController] I think this is not what you want.
Third, #IBAction is for Storyboard. Why you are not using Storyboard in generell? What you want to achieve is possible within a few clicks and without one line of code.
After spending more time and looking for what I did wrong from #DonMag's answer, the view controller I was trying to push is inheriting the ViewController class instead of actual UIViewController.
I am building an iOS app, deployment target 12.1, swift 4.2. The app uses container views and has a navigation bar at the top of the main screens, preferably right under the status bar. In the launchscreen storyboard, I have constrained Navigation Bar.top to Safe.Area.Top. That works fine. But after I set the containerViewController to be the rootViewController in the AppDelegate, the navigation bar as I've constrained it in Main.storyboard (Navigation Bar.top to Safe.Area.Top) appears far below where it should be.
The only way I can get the navigation bar to appear right under the status bar is to create a custom frame for my window in AppDelegate with a negative y-value -- and that is definitely NOT a solution I'm comfortable with.
This seems to generate a y-value too low:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let containerViewController = ContainerViewController()
window!.rootViewController = containerViewController
window!.makeKeyAndVisible()
return true
}
And this is the egregious hack that gets the navigation bar closer to where it should be:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//window = UIWindow(frame: UIScreen.main.bounds)
let hackedFrame = CGRect(x: 0, y: -44, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
window = UIWindow(frame: hackedFrame)
let containerViewController = ContainerViewController()
window!.rootViewController = containerViewController
window!.makeKeyAndVisible()
//window!.windowLevel = UIWindow.Level.statusBar
return true
}
Screen grabs:
I'm probably missing something really obvious here, but I'd appreciate any help anyone can give.
Thanks.
In iOS 11 Apple introduced large titles in the navigation bar, which means that it can be stretched if pulled. You should try setting
navigationItem.largeTitleDisplayMode = .never
In your viewDidLoad, and set the prefersLargeTitles of your navigation bar to false
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
try adding the navigation bar in ViewController instead of AppDelegate like this:
var screenWidth : CGFloat!
var screenHeight : CGFloat!
let screenSize: CGRect = UIScreen.main.bounds
inside ViewDidLoad:
screenWidth = screenSize.width
screenHeight = screenSize.height
navigationBar = UINavigationBar(frame: CGRect(x: 0, y: 20, width: screenWidth, height: screenWidth / 3))
for adding title and button:
view.addSubview(navigationBar)
let navItem = UINavigationItem(title: "MainController")
let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel, target: nil, action: #selector(DismissViewController))
navItem.leftBarButtonItem = doneItem
UINavigationBar.appearance().barTintColor = .white
navigationBar.setItems([navItem], animated: false)
I'm writing my app router like this:
final class AppRouter {
let navigationController: UINavigationController
init(window: UIWindow) {
navigationController = UINavigationController()
window.rootViewController = navigationController
...
}
I'm calling router initialiser in application:didFinishLaunchingWithOptions: method.
I was trying to change it style (colour, font and other) by changing it properties, child properties, using UINavigationBar.appearance()
Nothing works. I was setting translucent to false. Only storyboard changes are making any effect, but then I have storyboard based navigation, that I don't want to have.
I have seen many posts about this issue, nothing is working.
If someone have cookbook, that is working on newest iOS (currently 11.4), please share!
Edit:
Like I said making changes like:
UINavigationBar.appearance().barTintColor = color
UINavigationBar.appearance().isTranslucent = false
this is used in didFinishLaunching.
or in constructor:
navigationController.navigationBar.barTintColor = color
Both methods fail to set color of navigation controller bar.
Edit 2:
App delegate calls:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window
window.makeKeyAndVisible()
appRouter = AppRouter(window: window)
return true
}
Use below extension for UINavigationController
extension UINavigationController
{
func setMainTopNavigationBarAttribute() -> Void
{
self.navigationBar.shadowImage = UIImage()
self.navigationBar.isTranslucent = false
self.navigationBar.barTintColor = UIColor.black
self.navigationBar.tintColor = UIColor.white
self.navigationBar.backgroundColor = UIColor.clear
let navBarAttributesDictionary: [NSAttributedStringKey: Any]? = [
NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.font: UIFont(name: "HelveticaNeue-Bold", size: 18.0)
]
self.navigationBar.titleTextAttributes = navBarAttributesDictionary
}
}
final class AppRouter {
let navigationController: UINavigationController
init(window: UIWindow) {
navigationController = UINavigationController()
window.rootViewController = navigationController
navigationController.setMainTopNavigationBarAttribute()
}
When I try to add a custom UINavigationBar to a view controller like this
class ViewController: UIViewController
{
static let nav_bar_height: CGFloat = 64
let nav_bar: UINavigationBar =
{
let nav_bar = UINavigationBar()
nav_bar.translatesAutoresizingMaskIntoConstraints = false
nav_bar.backgroundColor = .blue
return nav_bar
}()
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .yellow
view.addSubview(nav_bar)
nav_bar.heightAnchor.constraint(equalToConstant: ViewController.nav_bar_height).isActive = true
nav_bar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
nav_bar.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
nav_bar.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
}
}
two distinct bars show up.
Any idea on why there is the white colored bar with a smaller height?
This is the AppDelegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let vc = ViewController()
window?.rootViewController = vc
// Override point for customization after application launch.
return true
}...
You're probably in navigation interface with a UINavigationController as your view controller's parent.
So the second navigation bar is the UINavigationController's navigation bar.
I am trying to learn Swift and iOS Views and ViewControllers.
var window: UIWindow?
var rootViewController: MyCustomView?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
// Override point for customization after application launch.
self.rootViewController = MyCustomView()
self.rootViewController!.backgroundColor = UIColor.orangeColor()
var rect = CGRectMake(20, 20, 100, 100)
var label = UILabel(frame: rect)
label.text = "Hello iOS Views"
label.backgroundColor = UIColor.orangeColor()
self.window!.rootViewController = self.rootViewController
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
I am getting an error when I compile, "Could not find member 'rootViewController'" on the following line:
self.window!.rootViewController = self.rootViewController
Not sure why Xcode 6 Beta is not liking it but it's able to find this line:
self.rootViewController!.backgroundColor = UIColor.orangeColor()
The problem is that MyCustomView is a UIView. But UIWindow's rootViewController expects a UIViewController.
Generally you have confused yourself right through your code by not distinguishing view controllers from views. But you did name MyCustomView sensibly, which is good. The fact that it has a backgroundColor helps to prove that it is a view, not a view controller (view controllers have no background color).