How to make `NSViewController` a `#MainActor` and have it implement `NSMenuItemValidation` - swift

I've a custom NSViewController subclass that implements the NSMenuItemValidation protocol. That works just fine, until I mark the view controller as #MainActor.
I believed Apple marked all view controllers as main actor isolated already, but maybe it does so only on iOS. But I thought it would be safe to mark my subclass as such.
Once I add the #MainActor line, I get the warning Instance method 'validateMenuItem' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'NSMenuItemValidation':
#MainActor
final class MyViewController: NSViewController, NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
}
}
Is it not okay to mark a custom NSViewController subclass as #MainActor? Or how should I deal with this warning?
I want to mark my view controller, because it needs to co-operate with other classes that are (explicitly) isolated to the main actor and I want to avoid various async boundaries that are totally not necessary, knowing the view controller will always run on the main thread.

You must mark the function w/ nonisolated. I'm assuming this is because the NSMenuItemValidation protocol is not restricted to the main queue.
nonisolated func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { }

Related

How to share a builder class across multiple instances in swift

I am trying to become a better developer and just started to study and use software patterns in small test projects.
In my current project I focused on the builder pattern.
Basically all I have is an App with two Views (Storyboards), A single ViewController-Class (used by all of the views) and one Builder-Class that creates an object with two properties.
The Builder-Class is implemented in the ViewController.
class ViewController: UIViewController {
#IBOutlet var startTimePicker: UIDatePicker!
#IBOutlet var workingHoursPicker: UIDatePicker!
#IBAction func setStartTimeButtonPressed(_ sender: Any) {
setStartTime()
}
#IBAction func setWorkingHoursButtonPressed(_ sender: Any) {
setWorkingHours()
}
var workdayBuilder: WorkdayBuilder = ConcreteWorkdayBuilder()
....
My current problem is that every time I navigate to the next View (using a Segue) the ViewController will be instantiated again and loses the value I set in the previous view.
I know that I could technically just pass the value to the next ViewController and then set it there or that I could set the value and then pass the instance of my builder to the next view. But that would kind of destroy the purpose of the builder in this project IMO.
I then thought that I could wrap my Builder into a Singleton and use it like a shared instance across the views:
final class WorkdayBuilderSingleton {
static let sharedInstance: WorkdayBuilder = ConcreteWorkdayBuilder()
init() {}
}
But this lead to another error in my VC when I tried to set a property. "Cannot assign to property: 'sharedInstance' is a 'let' constant".
So I am left with some questions and hope you can help me.
Is it a good or OK practice in general to make a builder a singleton or did I make a bad mistake here?
If it is OK, can I change mit static let into a static var or would that kill the singleton?

How to avoid calling `awakeFromNib` twice when view uses a separate `xib` for its TouchBar

Background
I'm adding a TouchBar to an app. I have a few window controllers that should use the same TouchBar design, adopt the same protocol (say AddWindowCommon), and are designed in their own xib file.
I designed my TouchBar in a separate xib file (AddWindowTouchBar). This way I can set the file to only compile on macOS 10.12.2+ (the versions that support the TouchBar) while the rest of the project deploys on macOS 10.9+. The file owner adopts the AddWindowCommon protocol and the touchbar keeps a weak reference to it to function properly. Then, to use this touchbar, I override the makeTouchBar as such:
#available(OSX 10.12.2, *)
open override func makeTouchBar() -> NSTouchBar? {
return AddWindowTouchBar.instanceFromNib(withWindowController: self)
}
That calls the following static function of my AddWindowTouchBar class:
static func instanceFromNib(withWindowController windowController: AddWindowCommon) -> AddWindowTouchBar {
var objects = NSArray()
Bundle.main.loadNibNamed(xibName, owner: windowController, topLevelObjects: &objects)
// ...
// return the touchbar from `objects`
// ...
}
Question
The problem here is that a class that adopts the AddWindowCommon protocol has its awakeFromNib: method called twice: a first time when it is loaded from its own xib, and a second time when the AddWindowTouchBar is instantiated using loadNibNamed:owner: and the window controller is the owner.
To avoid performing the tasks in awakeFromNib: twice, I could use a simple boolean that ensures the class hasn't been awakened from a nib yet, but I was wondering if there was another cleaner solution that acted on the AddWindowTouchBar rather than on the AddWindowCommon protocol-adopters.

performSegueWithIdentifier from a NSView subclass?

I have a document window that contains a number of NSView subclasses, switched between using a tab control. Each of the subclasses, and the window's ViewController, support different user actions accessed through menu items tied to the First Responder.
I'd like to perform a segue from one of those views in response to a menu item. However, NSView does not support performSegueWithIdentifier, it appears to be something that is part of NSViewController alone.
Can someone suggest a way around this? I have seen suggestions to pass the VC into the views, but I am not clear how to do that. Or perhaps there is a better way?
view.containingController.performSegue()
note: you have to add containingController to your views
I WOULD add the viewController to the responder chain and then make containingController a computed property in an extension!
e.g. add vc as responder:
override func awakeFromNib() {
super.awakeFromNib()
self.nextResponder = self.view
for subview in self.view.subviews {
subview.nextResponder = self
}
}
e.g. containingController in extension
extension NSView {
var containingController: NSViewController? {
get {
while(self.nextResponder != nil) {
if(self.nextResponder is NSViewController) {
return self.nextResponder
}
}
return nil
}
}
}
You could do that (see Daij-Djan's answer), however it is not what I would recommend, since a hypothetical programmer who will be using your code, but is not familiar with it (let's say, you in a year :) ) might be caught by surprise by such behaviour.
I would recommend you to add a delegate (conforming to your custom protocol, let's call it MyViewDelegate) to your NSView with a method like viewRequiresToPerformTransition(view: YourViewSubclass). Then you implement this method (more generally, you conform to MyViewDelegate protocol) in your view controller and inside its implementation perform any segue you want.

Swift: property that is a UIView subclass implementing a protocol

Here's the scenario. I have a number of different views I want to show, depending on the model object I'm showing the user. So I've set up a protocol whereby any view that implements it can be presented.
class MyItem { /* some model properties */ }
protocol ItemView: class {
// some protocol methods (e.g. updateWithItem(), etc)
func setupItem(item: MyItem)
}
class SpecificItemView: UIView, ItemView { /* there will be multiple classes like this */
func setupItem(item: MyItem) {
// do item setup
}
}
class AnotherItemView: UIView, ItemView {
func setupItem(item: MyItem) {
// do DIFFERENT item setup
}
}
Then when I go to use them in a view controller, I've been given one of my ItemView classes:
class MyViewController: UIViewController {
var itemView: ItemView? // could be a SpecificItemView or AnotherItemView
override func viewDidLoad() {
itemView?.setupItem(MyItem())
itemView?.removeFromSuperview() /* this line won't compile */
}
}
Everything works except that last line, where I try to call up to a UIView method (removeFromSuperview). No surprise there, since ItemView has no relation to UIView.
In Objective C I would solve this issue by specifying my itemView ivar like so:
#property (nonatomic, strong) UIView<ItemView> *itemView;
But I can't seem to find a similar syntax for Swift. I suspect there is no way to use this pattern in Swift. How do I achieve my overall goal of interchangeable UIView classes in a Swift-friendly manner?
One hacky solution I've found so far, is to add any UIView methods I call (e.g. removeFromSuperview) to my ItemView protocol.
Another suggestion I got, not on SO, from Maurice Kelly, was to make a UIView subclass, implementing the ItemView protocol, that both SpecificItemView and AnotherItemView can descend from. You can see it implemented in this gist. While that solves the issue of encapsulating class AND protocol in a single type (e.g. var itemView: ItemViewParentClass) it basically makes the protocol pointless, since you're now implementing all the protocol's methods in your parent class and overriding them in your subclasses. The single biggest drawback of this solutions is that you have to cast instances of subclasses (SpecificItemView) to the new hypthetical parent class (ItemViewParentClass) when you refer to them in your View Controller.
As of Swift 5, this is possible by constraining your protocol to only be applied to UIViews. To do this, just do the following:
protocol ItemView: UIView {
func setupItem(item: MyItem)
}
By adding this constraint to your protocol, any object that the swift compiler recognizes as an ItemView will also have all UIView methods available on it.
Similarly, if a non-UIView subclass attempts to implement the ItemView protocol, your code will not compile.
As you're discovering, there's no way to specify both a class and a protocol for an object.
The easiest way to fix the problem is to add removeFromSuperview (and any other UIView methods you need to insure are present) to your protocol.
Perhaps the following (in Swift 4) would do the trick?
class MyViewController: UIViewController {
typealias T = UIView & ItemView
var itemView: T?
}
ItemView does not extend UIView, but your SpecificItemView does. Naturally, no removeFromSuperView exists on your ItemView.
In your ViewController you declare an ItemView type object, rather than a SpecificItemView
Change your MyViewController code to:
var itemView:SpecificItemView?

How do I handle callbacks when using selectors?

I'm placing my logic code in a viewModel. The view calls one method in the viewController. That method then calls the rest of the methods in the viewModel by using #selectors. This works fine up until the tableView needs to be reloaded with tableView.reloadData(). That part obviously needs to be in the view.
Normally, this would be accomplished by using multiple closures. But as #selectors can't have parameters I can't have a completion() callback in the last method that is called. So, my question is, how do I get around this problem? Is there any good alternatives to using #selectors? Should I have an observer in the view subscribing to the last method of the viewModel? Is RxSwift an alternative? Or is there a workaround using #selectors?
RxSwift is a good alternative, but in case you need something not as heavy, the delegate pattern is what you need:
protocol ViewDelegate {
// Other functions you might need
func reloadTableView()
}
Then in your viewController, you implement these:
class ViewController: ViewDelegate {
func reloadTableView() {
tableView.reloadData()
}
}
And somewhere, in your view model you need to define the delegate:
weak var viewDelegate: ViewDelegate
As well as assign it when creating the classes:
let model = ViewModel()
let view = ViewController()
model.viewDelegate = view
Swift official documentation has a lot more on protocols: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html