uiview-layout.md 7.2 KB

code layout thoughts

view definitions

  • UIView

    • basic element to build UI, buttons, labels, layouts etc.
    • rectangle area
    • can contain other UIView objects
    • similar to "View" on android
  • UIViewController

    • contains at least one UIView
    • similar to "Acticity" on android
  • UITabBarController

    • manages a number of UIViewController accessible via a button bar (abottom)
    • typically not subclassed, says apple
  • UINavigationController

    • manage a stack of UIViewController and a navigation bar (atop)
    • performs horizontal view transitions for pushed and popped views (via pushViewController(), popViewController())
    • typically subclassed, says apple
  • "delegate"

    • a "delegate" is a set of functions, defined by a protocol, to receive events as didSelect() [2]
    • the functions in the "delegate" are called by the "delegator"
    • both, "delegator" and "delegate" may be view controllers, eg. ViewControllerA ("delegator") is sending an event to ViewControllerB ("delegate")
    • in practise, subclasses just override functions defined by "delegate" protocol, as didSelect()
    • used view controllers the system provides
  • "coordinator"

    • convention, no requirement from UIKit
    • defined as an empty protocol class from where other derive
    • they typically take a UINavigationController as argument on construction and call eg. pushViewController() to navigate to other view controllers
    • stored as a member of the view controller
    • everything done there could also be done in a view controller directly
    • when derived from an abstract base class, in theory, this helps on making things more reusable by removing the navigation from the view, see next point
  • "coordinator protocol"

    • convention to define a base for coordinators having the same interface
    • eg. ContactDetailCoordinatorProtocol

app definitions

  • UIApplication

    • every app has exactly one instance of UIApplication [1]
    • typically not subclassed
    • created by calling UIApplicationMain() or by adding the attribute @UIApplicationMain to a class derived from UIApplicationDelegate
    • main entry point is application(_:didFinishLaunchingWithOptions:) [4]
    • similar to "Application" on android
  • UIApplicationDelegate

    • a "delegate" (see "view delegate" for definition) for events on application-level as applicationWillEnterForeground()
    • we need to derive from UIApplicationDelegate and pass this to UIApplicationMain
    • UIApplication.shared.delegate will keep a reference to our delegate
    • we can store "app globals" here, as DcContext
  • UIWindow

    • backdrop for the ui, dispatches events, derived from UIView
    • stored in UIApplication.shared.delegate.window,
    • created in application(_:didFinishLaunchingWithOptions:); in pure iOS13 apps set in SceneDelegate
    • an app typicaly has only one window
    • window.rootViewController is the anchor of all view controllers

holding references

a tricky part (see eg. [3]) seems to be to hold the correct type of references to the UIViewControllers.

  • at least one "strong" (normal) reference is needed somewhere.

  • only "weak" or "unowned" references are not sufficient ("weak" always needs unwrap and may be come nil at any time, "unowned" is kind of always-unwrapped "weak").

  • currently, we hold strong references to the coordinators which hold strong references to the view controllers

what is needed? what adds unneeded complexity?

  • "delegates" are needed to get events from the system

  • we need some dead-simple way to persist the objects that need persistance

  • do we need our own delegates? or just call funcions directly as needed? what is the current state?

  • is the overhead of "coordinator" really needed and helpful?

    • why not just call eg. pushViewController() and make sure it is persisted as needed (coordinators would not help on that imu anyway, see bug at [3])
    • TODO: AppCoordinator is also derived from NSObject - is there a know reason for that?
    • seem not to help on making code readable - esp. as all coordinators are in a separate file
    • i think the "coordinator" as they are used now are of very limited use,
  • however, the coordinator get the UINavigationContoller as argument, so there is some use

  • remove "coordinator protocol" and do not use?

    • in general, for some minor adaptions, a simple flag eg. hiding some elements appear much more readable to me that having several levels of abstraction
    • when we really come to the point where we need to reuse things, we can add these things as needed (or use parameters on creation "flag"), i think, in the current state, coordinators create more harm than use.
  • the tearing up of navigator/coordinator into two files is unfortunate, in swiftUI, this is re-combined to a single file, child-class and a makeCoordinator() called by SwiftUI: https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

notification system

  • NotificationCenter.default.post() post events to all observers synchronously on the same thread; this may not be the thread the observer registered itself [5]. Observers may receive the event in any random thread therefore.

  • best practise [6] would be that the observers assume, they receive the notification in any thread and call DispatchQueue.main.async where actually needed. for new code, we should use that approach, it won't hurt.

  • however, existing code might assume that the notification arrives in main thread; therefore, we also call DispatchQueue.main.async for sending events out.

  • using always main thread for sending/receiving and adding/remove observers also avoids other multithreading-issues of NotificationCenter itself, see https://lapcatsoftware.com/articles/nsnotificationcenter-is-threadsafe-not.html for details.

tl;dr: post from main thread and also add/remove observers from main thread. on receiving, assume, things will block.

notification system: adding and removing observers

  • for the addObserver() variant returning an object (overview at https://developer.apple.com/documentation/foundation/notificationcenter ) always save the object as NSObjectProtocol not as Any? - otherwise you risk to mix addObserver() functions as also the non-return variant can be saved as Any? (Swift allows saving "no return type" as Any?)

  • call removeObserver(self) only in deinit, otherwise use removeObserver(self, name, obj) or removeObserver(obj)

some sources

[1] https://developer.apple.com/documentation/uikit/uiapplication

[2] delegator/delegate: https://stackoverflow.com/questions/7052926/what-is-the-purpose-of-an-ios-delegate https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html

[3] weak coordinator bugs: https://github.com/deltachat/deltachat-ios/issues/675, https://github.com/deltachat/deltachat-ios/issues/323

[4] https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622921-application

[5] https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Notifications/Articles/Threading.html#//apple_ref/doc/uid/20001289-CEGJFDFG

[6] https://medium.com/@hadhi631/myths-and-facts-about-nsnotifcation-posting-and-receiving-df7f5729b19f