Update UIWindow var in AppDelegate from another target - ios

A swift iOS app has 3 targets - AppTarget, Main (static lib which contains the entry point i.e. AppDelegate) and Presentation (static lib which contains UI code). AppTarget depends on Main and Presentation. Presentation depends on Main.
Question is, how do I set the UIWindow variable in Main from Presentation?
#main
public class AppDelegate: UIResponder, UIApplicationDelegate {
// UIWindow view of UIApplicationDelegate protocol
var window : UIWindow?
...
}
I tried two approaches:
Approach1:
Directly access the UIWindow variable using the AppDelegate object, but it gives 'Cannot assign to property: 'shared' is a get-only property' error:
UIApplication.shared.delegate?.window = UIWindow() // Error: Cannot assign to property: 'shared' is a get-only property
Approach2:
Use an intermediate static variable like:
// In AppDelegate,
static var keyWindow : UIWindow? = nil
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Set UIWindow variable to the static instance.
window = AppDelegate.keyWindow
// Now, instantiating AppDelegate.keyWindow is the same as instantiating window (?).
...
return true
}
// Elsewhere, in Presentation,
func CreateWindow() {
window = AppDelegate.keyWindow
AppDelegate.keyWindow = UIWindow()
AppDelegate.keyWindow?.rootViewController = ViewController()
AppDelegate.keyWindow?.makeKeyAndVisible()
}
// ViewController has a simple UI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
}
}
This gives me a black screen, meaning, there's no UI...an empty screen. But I expected to see a green screen.
I understand that UI has to be set in the UIWindow variable of the AppDelegate (if you use another UIwindow variable, its contents are not displayed and you'd get an empty black screen), due to its conformance to UIApplicationDelegate protocol.
How can I achieve this elegantly?
Note1: Assume I've opted out of scenes
Note2: Ignore how AppDelegate in Main invokes CreateWindow() in Presentation, but it somehow does.

Related

How to start a new project using AppDelegate and UIKit in Xcode 14.2 [duplicate]

This question already has answers here:
How to remove Scene Delegate from iOS Application?
(6 answers)
Closed last month.
Edit
I mistakenly selected "Multiplatform" instead of "iOS" when creating the project.
Simply select "iOS" to create the project and things will be done the traditional way.
I want to start a new project in Xcode 14.2 with just AppDelegate and UIKit, without SwiftUI or SceneDelegate.
The method up to Xcode 13 does not seem to work.
1. Create new project in Xcode 14.2
2. Add AppDelegate.swift
import UIKit
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window!.rootViewController = MyViewController()
window!.makeKeyAndVisible()
return true
}
}
3. Add MyViewController.swift
import UIKit
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
print("viewDidLoad() called.")
print("frame:", view!.frame)
}
}
When the application is launched, a green screen is expected to appear, but a black screen is displayed.
Print statements are output correctly.
At this time, the following warning appears.
Adding UIApplicationSceneManifest key to Info.plist will remove this warning, but the black screen will remain. (Previously, this key was not needed for apps that did not use multiple windows.)
[SceneConfiguration] Info.plist contained no UIScene configuration dictionary (looking for configuration named "(no name)")
What simple something am I missing?
I assume you want to invoke your app by initializing a UIViewController and don't care much about the underlying UIResponder objects.
Create a new project by selecting Storyboard as the desired Interface preference (You won't use a storyboard after all).
Then add your first View Controller to the window object. And if you'd like to access your AppDelegate you can do it from anywhere as below.
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let window = window else { return }
let vc = UIViewController()
vc.view.backgroundColor = .systemGreen
window.rootViewController = vc
window.makeKeyAndVisible()
let appDelegate = UIApplication.shared.delegate as? AppDelegate
}
}
Correct me if your intention of omitting UIWindowSceneDelegate is of other nature, such as supporting iOS 12

How to keep a UIView always showing over multiple views

I wish to create and present a view over each view controller in my app allowing users to interact with it or the view controllers beneath it. I want the view to always appear on top of whatever view I may present or segue to.
I have a custom UIView that appears when users tap a table view cell. Within the didSelectRowAt tableView function I have tried:
(UIApplication.shared.delegate as! AppDelegate).window?.addSubview(self.subView!)
and
self.view.addSubview(self.subView!)
Both function similarly with the view appearing over the current View controller and allowing users to interact with the table still. But when I present a new ViewController the subView disappears as it has not been added to the new View.
Subclass UIWindow, and override addSubview() to make sure your overlay view is always on top:
weak var overlay: MyOverlayView!
override func addSubview(_ view: UIView) {
if let overlay = view as? MyOverlayView {
self.overlay = overlay
super.addSubview(overlay)
} else {
self.insertSubview(view, belowSubview: overlay)
}
}
You must make sure the app delegate uses your custom class as the app's main window, and not the superclass UIWindow. For this, remove the "Main storyboard file base name" entry from your Info.plist and instead instantiate the main window manually in your AppDelegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: MyCustomWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
self.window = MyCustomWindow(frame: UIScreen.main.bounds)
// 1. Add your overlay view to the window
// (...)
// 2. Load your initial view controller from storyboard, or
// instantiate it programmatically
// (...)
window?.makeKeyAndVisible()
return true
}
Note: I haven't tested this code. Let me know in the comments if there's a problem.

Instantiate UINavigationController in AppDelegate

I'm trying to instantiate a UINavigationController with a rootViewController in my AppDelegate. I've looked on this website, but all the examples were from Objective-C or used storyboards (which I'm trying to get away from). 'HomeScreenController` is the root view of the application.
Edit: this shows up in the console
2015-07-18 14:42:25.376 FastFactsSwift[4749:343495] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main'
- perhaps the designated entry point is not set?
How do I fix this?
The following code results in just a black screen:
AppDelegate.swift:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window?.rootViewController = UINavigationController(rootViewController: HomeScreenController())
return true
}
...
HomeScreenController.swift:
import UIKit
class HomeScreenController: UIViewController, UISearchBarDelegate, UITableViewDelegate{
override func viewDidLoad(){
super.viewDidLoad()
var width = self.view.viewWidth
var height = self.view.viewHeight
//Add stuff to the view
}
...
Why is HomeScreenController showing up as a black screen?
The problem was not configuring AppDelegate to not use a storyboard.
How do I create a new Swift project without using Storyboards?

How to call a method of another ViewController from Appdelegate?

From my appdelegate I need to execute a method that is from a viewcontroller.
Please, can anybody tell me how to easily have the possibility to call whatever method I need from my appdelegate?
A lot of questions regarding this argument but none of these are useful/complete/right, so please avoid to post URL to other topics: I already checked all questions but I really can't find any precise answer regarding doing this in Swift. :)
It depends on how you've arranged your view controllers but here's an example from a simple iPhone master/detail project.
let root : UINavigationController = self.window!.rootViewController! as UINavigationController
let master : MasterViewController = root.topViewController as MasterViewController
println("\(master.description)")
The way I did it was to search for the view controller I want, starting on AppDelegate's var window: UIWindow? and then going deeper until I find it. I originally tried to implement NSNotification but this is not recommended on swift (I think computed property is a good replace for that, but it don't work in this case).
This is how I did for my tab based application with a NavigationController on top of my ViewController:
if let rootViewController = self.window?.rootViewController as? UITabBarController
if let viewControllers = rootViewController.viewControllers {
for navigationController in viewControllers {
if let yourViewController = navigationController.topViewController as? YourCustomViewController {
if yourViewController.hasSomeFlag {
yourViewController.theMethod()
}
break
}
}
}
}
You can do it with notifications, just add observer to your viewcontroller, post a notification from your app delegate, this observer will catch it and run a function you specify.
this tutorial should help get you started: https://www.codefellows.org/blog/how-to-implement-an-nsnotification-observer-in-swift
Try this
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// Override point for customization after application launch.
return true
}
func customMethod()
{
}
}
Call custom method from ViewController
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
appDelegate.customMethod()
}

Swift dynamic cast failed UIPageViewController

I am trying to cast my window.rootViewController as a UIPageViewController but anytime I access the class property which I defined it blows up with swift dynamic cast failed. My storyboard has a UIPageViewController is the initial scene.
PageViewController is just a subclass of UIPageViewController
class AppDelegate: UIResponder, UIApplicationDelegate, UIPageViewControllerDataSource {
var window: UIWindow!
var pageViewController: PageViewController {
return window.rootViewController as PageViewController
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
pageViewController.dataSource = self
return true
}
}
The main issue actually was because I was subclassing UIPageViewController I had to add the custom class to the ViewController inside my storyboard. At first I figured that since PageViewController is a UIPageViewController, and I am just casting the rootViewController to a PageViewController, it should've of worked. By adding the custom class it fixed my problem.
I also tested by not subclassing, it worked with out specifying a custom class.

Resources