I have an app where the UI is setup programmatically instead of using Storyboards. I am stuck on how to know when a tab has been switched... and when it has been switched I want to be able to assign a property called "userSettings" in each view controller so that I can pass this object around and call methods on it appropriately.
Here is my app delegate:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var contentVC: BMContentViewController?
var settingsVC: BMSettingsViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
contentVC = BMContentViewController()
contentVC?.view.backgroundColor = .clear
contentVC?.tabBarItem.title = "Content"
settingsVC = BMSettingsViewController()
settingsVC?.view.backgroundColor = .clear
settingsVC?.tabBarItem.title = "Settings"
let tabbarController = UITabBarController()
tabbarController.viewControllers = [(contentVC ?? UIViewController()), settingsVC ?? UIViewController()]
self.window?.rootViewController = tabbarController
self.window?.makeKeyAndVisible()
return true
}
func applicationWillResignActive(_ application: UIApplication) {}
func applicationDidEnterBackground(_ application: UIApplication) {}
func applicationWillEnterForeground(_ application: UIApplication) {}
func applicationDidBecomeActive(_ application: UIApplication) {}
}
BMContentViewController() and BMSettingsViewController() each have a property called:
var userSettings: BMUserSettings?
I start the app off in BMContentViewController(). When I switch tabs, I want to be able to assign the userSettings in BMSettingsViewController() to the userSettings that is in BMContentViewController(). userSettings is a central object in my app and instead of creating a singleton, I'm trying to get the app to work by just passing a reference to the object around to other view controllers.
I'm not sure if this is possible by detecting when the tab was switched and passing the value or if I need to use the delegate pattern instead?
How can I accomplish this? I'm trying to avoid using singletons.
You can pass data between view controllers in 6 ways:
Instance property (A → B)
Segues (for Storyboards)
Instance properties and functions (A ← B)
Delegation pattern - you can try delegate pattern, this will be my first best option
Closure or completion handler
NotificationCenter and the Observer pattern - this will be the second best option in your case.
Related
I'm just trying to understand the general architecture of UIApplication. My understanding of using a delegate works something like following:
protocol MyDelegate {
func someProtocolMethod()
}
class SomeClass {
var delegate: MyDelegate!
init(){
}
func someClassMethod(){
self.delegate.someProtocolMethod()
}
}
class ClassConformingToDelegate: NSObject, MyDelegate {
let someClass: SomeClass
override init(){
someClass = SomeClass()
super.init()
someClass.delegate = self // self has to be assigned so that SomeClass's delegate property knows what the conforming class is
}
func someProtocolMethod(){}
}
In a similar fashion, AppDelegate conforms to UIApplicationDelegate by having a number of protocol methods implemented.
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
UIApplication declares the delegate as following in its class:
unowned(unsafe) var delegate: UIApplicationDelegate?
But, in order for this delegate to know that AppDelegate.swift is the true delegate, UIApplication has to be instantiated and AppDelegate.swift be assigned to the instance, similar to the example above. So something like the following should happen within AppDelegate.swift:
let application = UIApplication()
application.delegate = self
But, how is this step omitted and AppDelegate still works?
The answer to this question varies a little depending on which version of Xcode/Swift/iOS you are talking about, but the essential process is the same.
If you create a project in Xcode that uses the UIKit AppDelegate lifecycle then you will see the line #main at the start of the AppDelegate.swift file.
This tells the compiler that this file contains the UIApplicationDelegate implementation. The compiler then synthesises a main function for you that performs all of the required setup, including creating an instance of the AppDelegate and assigning it to the UIApplication instance.
In earlier versions of Swift you would see #UIApplicationMain that does essentially the same thing.
You can omit the #main/#UIApplicationMain and create your own main that does all of the required work, but this generally isn't required.
With SwiftUI you now have the option of using SwiftUI lifecycle rather than UIKit lifecycle when you create the project. In this case you have an App struct. This file still contains the #main and is used to launch your app's view hierarchy.
Hello I was wondering if there is a way or how to make a tabBarControllerView once the user logs in programmatically without using storyboard. After doing research I found out how to do it by setting in the appDelegate the tabBarViewcontroller class as rootview. But my root view is the login screen. I was wondering if there was a way to do create a tabBarView and load it once the app reaches a certain ViewController/screen.
Here is my tabBarViewController class so far:
import Foundation
import UIKit
class tabBarControllerView: UITabBarController{
override func viewDidLoad() {
super.viewDidLoad()
UINavigationBar.appearance().prefersLargeTitles = true
viewControllers =
[UserProfileControllerUIKIT(),MainPageUIKIT(),OtherPageUIKit()]
}
}
Here is my appDelegate:
//
import UIKit
import Firebase
#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.
FirebaseApp.configure()
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = tabBarControllerView()
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
You can change rootviewcontroller after login
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarControllerView()
appDelegate.window?.makeKeyAndVisible()
Working with Xcode 10.1 and Swift 4.2
I have a complex app that uses a UINavigationController implemented in the AppDelegate.
The rootViewController of the navigationController is a DashboardController() class (subclass of UIViewController)
The DashboardController implements a left menu drawer using several ViewControllers (with self.addChild(viewController))
Everything works fine, except when I need to push a viewController to present a BarCodeScannerView().
The barebone barCodeScannerView can be pushed and popped as expected.
The problems arises when I request access to the camera (only the first time).
As soon as I present the Device.requestAccess(for:) as follow: the viewController is popped and the previous view (rootViewController) is presented. (Still with the "App would like to access the camera" AlertView)
func requestCameraAccess() {
AVCaptureDevice.requestAccess(for: AVMediaType.video) { granted in
if granted {
self.launchScanner()
} else {
self.goBack()
}
}
}
If I click "OK" The system will register that the access was granted, but the
applicationDidBecomeActive (in the AppDelegate) is called after aprox 1 second. I have some initializers in applicationDidBecomeActive, and they all are executed again. And after a quick delay, everything works fine.
BTW: applicationWillResignActive, applicationDidEnterBackground and applicationWillEnterForeground are NOT called. So it is clear that this is not part of an App LifeCycle.
Any idea what might me going on here? What can make the system call applicationDidBecomeActive within the app? and still keep everything running?
Thx in advance...
UPDATE After reading the comments, I was able to isolate the issue #2 as follows:
A simple/barebones project with a UINavigationController with a dashboardViewController as rootViewController. The dashboardViewController pushes a CameraViewController() in viewDidLoad(). The cameraViewController requests access to the camera. When clicking OK, the call to applicationDidBecomeActive is triggered.
The full project is attached. (except the "Privacy - Camera Usage Description" key in the .plist.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? = UIWindow()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let dashboardViewController = DashboardViewController()
window?.rootViewController = UINavigationController(rootViewController: dashboardViewController)
window?.makeKeyAndVisible()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
print("applicationDidBecomeActive")
}
func applicationWillResignActive(_ application: UIApplication) {}
func applicationDidEnterBackground(_ application: UIApplication) {}
func applicationWillEnterForeground(_ application: UIApplication) {}
func applicationWillTerminate(_ application: UIApplication) {}
}
class DashboardViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
let cameraVC = CameraViewController()
self.navigationController?.pushViewController(cameraVC, animated: true)
}
}
import AVFoundation
class CameraViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
AVCaptureDevice.requestAccess(for: AVMediaType.video) { granted in
if granted {
print("Access granted")
}
}
}
}
I'd say the problem is just with your testing procedure. When I run your code with a print statement in applicationWillResignActive, this is what I see:
applicationDidBecomeActive
applicationWillResignActive
Access granted
applicationDidBecomeActive
That seems completely in order and normal. It would have been weird to get a spurious didBecomeActive, but that is not what's happening; we resign active and then become active again, which is fine. You should expect that at any time your app can resign active and become active again. Many things in the normal lifecycle can cause that, and the presentation of an out-of-process dialog like the authorization dialog can reasonably be one of them. You should write your code in such a way as to cope with that possibility.
For example, in MainViewController which is a subclass of UIViewcontroller, I overrode the nib name variable and returned a string to satisfy the argument however in the appDelegate I was able to simply assign an object to the rootViewController variable without using the term override. I understand the keyword override means to rewrite a method which is inherited by the subclass however I'm not understanding it in this sense where we're using variables.
AppDelegate.swift code:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainViewController: MainViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//so as this function fires we need to put forth some effort.....from inside this function instantiate a view controller
let mainViewController = MainViewController()
//put the view of the ViewController on screen
//mainViewController.show(mainViewController, sender: self)
window?.backgroundColor = UIColor.purple
window?.rootViewController = mainViewController
//set the property to point to the viewController
self.mainViewController = mainViewController
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
MainViewController.swift code:
import Foundation
import UIKit
class MainViewController: UIViewController {
override var nibName: String? {
return "MainViewController"
}
}
nibName is a read-only property - checking the public UIViewController interface shows:
open var nibName: String? { get }
It doesn't make sense for the nibName to be able to change during the lifecycle of your UIViewController instance, so having it as a read-only property ensures it's simply a form of configuration.
I have a class named Home which is the parent class of my app. Now, I want to initialize this class somewhere so that I can access everything inside the class from wherever I want. The starting point of the app is RootViewController. Should I initialize the app in the starting point? If yes, how should I do it so that it can be accessed from everywhere in the app?
As per my comment above, set a property on the AppDelegate class with the type Home, initialize it in application:didFinishLaunchingWithOptions. Now you can access this instance of home through the sharedApplication.delegate.
In AppDelegate.swift:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var myHome: Home?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
// Override point for customization after application launch.
self.myHome = Home()
return true
}
Then access it in some other class:
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
var home = delegate.myHome