Swift - Programmatically creating Views Issue, unable to find stored property - ios

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).

Related

How do I add a view to the top of an app in iOS 13/14

I'm trying to add a view to my app that is persistent across flows and sits above all other views. In previous versions of iOS i've been able to simply add a subview to the UIWindow that sat atop my application however it would appear that as of iOS 13 this is no longer possible.
I've tried the following, but to no success:
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.addSubview(someView)
Debugging a bit further, it appears that my app does not have a window at all present on its view hierarchy. I know this because when putting a breakpoint on the follow forEach statement, I never get a breakpoint to execute.
UIApplication.shared.windows.forEach { (window) in
window.addSubview(myView)
}
Any advice on how to achieve this, on all iOS versions 11 through 14 would be much appreciated.
You have to create a second UIWindow, assign the rootViewController and make it visible.
The code bellow is for SwiftUI, but you can do the same with UIKit, just create a window and set window.isHidden = false.
let secondWindow = UIWindow(windowScene: windowScene)
secondWindow.frame = CGRect(x: 0, y: 40, width: UIScreen.main.bounds.size.width, height: 100)
let someView = Text("I am on top of everything")
secondWindow.rootViewController = UIHostingController(rootView: someView)
secondWindow.isHidden = false
Depending on how many windows you have. You might need to change the windowLevel of your second window.
You can check if the window is displayed using Debug View Hierarchy from Xcode.
This is a UIKit example without SceneDelegate. If you have scene delegate you have to pass the window scene to the UIWindow init.
Don't forget to retain the second window.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
private enum Constants {
static let sessionConfiguration = URLSessionConfiguration.default
}
var window: UIWindow?
var secondWindow: UIWindow?
// MARK: - UIApplicationDelegate
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window
let viewController = UIViewController()
viewController.view.backgroundColor = .red
window.rootViewController = viewController
window.makeKeyAndVisible()
let secondWindow = UIWindow()
secondWindow.frame = CGRect(
x: 0,
y: 40,
width: UIScreen.main.bounds.size.width,
height: 100
)
let secondController = UIViewController()
secondController.view.backgroundColor = .blue
secondWindow.rootViewController = secondController
secondWindow.isHidden = false
self.secondWindow = secondWindow
return true
}
}

UINavigationBar too low when using UIScreen.main.bounds as UIWindow frame

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)

Huge navigation bar on the iPhone

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

Change UINavigationController color and font

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()
}

UINavigationBar does not extend behind status bar

I feel like I'm completely overlooking something, since this is so basic.
In a completely bare bones setup:
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
let rootController = MainViewController()
rootNavigationController = UINavigationController(rootViewController: rootController)
window.rootViewController = rootNavigationController;
window.makeKeyAndVisible()
// Appearance
UINavigationBar.appearance().barTintColor = UIColor.DailyRate.blueColor
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UINavigationBar.appearance().opaque = false
I get a navigation bar that does not extend behind the status, which should be default behavior.
I just tried and I got proper result. Please find my complete code. I can not find few things in your code(I don't get what you mean by DailyRate) , remaining things are same as your code.
var window: UIWindow?
var rootNavigationController : UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window!.backgroundColor = UIColor.whiteColor()
let rootController = ViewController()
rootNavigationController = UINavigationController(rootViewController: rootController)
window!.rootViewController = rootNavigationController;
window!.makeKeyAndVisible()
// Appearance
UINavigationBar.appearance().barTintColor = UIColor.blueColor()
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UINavigationBar.appearance().opaque = false
// Override point for customization after application launch.
return true
}
And the result is in following attachment.
I encountered this problem because I was removing the built-in line border at the bottom of the navigation bar, like this:
if let navigationBar = self.navigationController?.navigationBar {
navigationBar.shadowImage = UIImage()
navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
navigationBar.backgroundColor = UIColor.redColor()
}
I was doing the above code inside my view controller's viewWillAppear because some of my VCs have other colors for the navigation bar and I don't want to modify the universal appearance.
The solution was to just create a 1 pt x 1 pt image with the color I want and use it instead of a new empty UIImage instance, like this:
if let navigationBar = self.navigationController?.navigationBar {
let colorImage = UIImage.imageWithColor(self.category.color)
navigationBar.shadowImage = colorImage
navigationBar.setBackgroundImage(colorImage, forBarMetrics: .Default)
navigationBar.tintColor = UIColor.whiteColor()
}
imageWithColor is a function I defined in an extension to UIImage:
class func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRectMake(0, 0, 1, 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
It turned out that it was a timing issue.
The root hierarchy was actually set up in the initializer of a separate class called UIManager. However, this class was initialized at the same time as the AppDelegate
var uiManager = UIManager()
and not in application(_, didFinishLaunchingWithOptions _) method, thus creating this weird scenario.
So all I did was
var uiManager: UIManager?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
uiManager = UIManager()
}
And now everything is back to normal.
Thanks to #govindarao-kondala for planting the right idea in my head!

Resources