I want replicate WindowButton actions like close, minimize and maximize with this code, but I have lots of issue and errors, like:
struct ContentView: View {
var body: some View {
Button("close") {
WindowButtonEnum.close.actionFunction(value: ContentView.self)
}
Button("miniaturize") {
WindowButtonEnum.miniaturize.actionFunction(value: ContentView.self)
}
Button("zoom") {
WindowButtonEnum.zoom.actionFunction(value: ContentView.self)
}
}
}
enum WindowButtonEnum {
case close, miniaturize, zoom
func actionFunction(value: Any?) {
switch self {
case .close: NSApplication.shared.keyWindow?.close
case .miniaturize: NSWindow.miniaturize(value)
case .zoom: NSWindow.performZoom(value)
}
}
}
Function is unused
Instance member 'miniaturize' cannot be used on type 'NSWindow'; did you mean to use a value of this type instead?
Instance member 'performZoom' cannot be used on type 'NSWindow'; did you mean to use a value of this type instead?
I want find the answer with using enum and more SwiftUI-isch coding style.
Related
I am trying to build a simple design system for my pet project to share between android and iOS apps.
The definitions of widgets are in the common Kotlin module.
I am trying to write swift UI functions to build actual views:
#ViewBuilder
func widget(widget: MyWidget) -> some View {
switch widget {
case let box1 as MyBox:
box(box: box1)
default:
fatalError("Unsupported widget: \(widget)")
}
}
#ViewBuilder
func box(box: MyBox) -> some View {
ZStack {
ForEach(box.children) { child in
widget(widget: child)
}
}
}
When compiling the project, I get the following error:
Command CompileSwift failed with a nonzero exit code
Removing the recursion (like replacing widget call with a Text) resolves the compilation problem.
Note that these functions are not called anywhere else.
XCode version 14.1
Removing recursive call fixes compiler error
Structs in Swift must have a size that is knowable at compilation. In other words, a struct cannot contain a variably-sized object. A struct can contain an array or string or other variably-sized object by using a constant-sized reference to said object (a pointer, essentially). You can't do this:
struct A {
var b: B
}
struct B {
var a: A
}
But you can do this, because both _a and _b are actually just pointers to arrays (i.e, constant size regardless of size of the array or the objects in the array), and A.b and B.a are computed properties that are not actually stored.
struct A {
var _b: [B]
var b: B {
get {
return _b[0]
}
set(new) {
_b[0] = new
}
}
}
struct B {
var _a: [A]
var a: A? {
get {
return _a[0]
}
set(new) {
_a[0] = new
}
}
}
And you can also do this, because storing a Box<T> value only stores a pointer to a Box<T> object, so the size of the reference does not depend on the size of the referenced value.
class Box<T> {
var value: T
init(value: T) {
self.value = value
}
}
struct A {
var b: Box<B>
}
struct B {
var a: Box<A>
}
Since SwiftUI views are structs, they cannot be recursive. There are multiple ways of creating recursive data structures in Swift, and I don't know enough about SwiftUI to say what approach is appropriate in your use case. You can box stuff in via a wrapper class or wrapping in an array, or you can create a recursive Enum box object via the indirect keyword:
enum Maybe<T> {
case None
indirect case Some(_ value: T)
}
struct A {
var b: Maybe<B>
}
struct B {
var a: Maybe<A>
}
let foo = A(b: .None)
let bar = B(a: .Some(A(b: .None)))
Let say I have two functions to create gestures:
func makeGesture1() -> some Gesture {
...
}
func makeGesture2() -> some Gesture {
...
}
and I want to use them like:
Text("abc")
.gesture( someCondition? makeGesture1() : makeGesture1().simultaneously(with: makeGesture2())
I got the error:
Result values in '? :' expression have mismatching types 'SimultaneousGesture<some Gesture, some Gesture>' and 'some Gesture'
If I wrap the expression with a function:
func makeGestureConditionally() -> some Gesture() {
if someCondition {
return makeGesture1()
} else {
return makeGesture1().simultaneously(with: makeGesture2())
}
}
I got the error:
Function declares an opaque return type, but the return statements in its body do not have matching underlying types
I found that I can do the following, but I wonder if there is a less hacky way, a proper way:
someCondition
? AnyGesture(makeGesture1().simultaneously(with: makeGesture2()).map { _ in () })
: AnyGesture(makeGesture1().map { _ in () })
When you use an "opaque return type" (like some Gesture), you are asserting to the compiler that you will only return 1 specific concrete Gesture type, you just don't want to write out its full signature.
This is why you cannot dynamically return different types at runtime.
This is why the ternary operator (?:) is failing as well; a ternary operator only accepts operands of the same type.
A workaround is use a SwiftUI ViewBuilder to let the SwiftUI runtime activate the correct gesture on the view based on the condition.
var baseView: some View {
Text("123")
}
#ViewBuilder
var content: some View {
if someCondition {
baseView
.gesture(makeGesture1())
} else {
baseView
.gesture(makeGesture1().simultaneously(with: makeGesture2()))
}
}
This is allowed as internally SwiftUI is using something called a "result builder" to only return 1 branch of the if statement at runtime, with each branch of the if statement having a concrete type.
In SwiftUI, avoid using any of the type erased wrappers (AnyView, AnyGesture etc.) as they increase the computational overhead on SwiftUI.
I am working on a SwiftUI project, where I use the MVVM-architecture.
When changing a View-model object property from the SwiftUI view, it causes a memory conflict crash in the view-model object.
The error is of the type: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
In steps, here is what happens:
View-model property is changed from view
View-model property changes model property
Model property notifies about changes
View-model receives change notification
View-model access model object
Crash occur due to memory conflict
Relevant code snippets are seen below. Xcode project is a standard SwiftUI project.
The error will happen, after first clicking the add button, and then the modify button.
If the "update" code is moved into the "receiveValue" closure, the error will not occur. Likewise, the error will not occur, if the View-model class is made non-generic.
To my best knowledge, the code is all-right, so I suspect it is a compiler problem. But I am not sure.
import Foundation
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var item: ViewModel<Model> = ViewModel<Model>()
var body: some View {
VStack {
Button("Add", action: { item.add(model:Model()) })
Button("Modify", action: { item.selected.toggle() })
}
}
}
protocol ModelType {
var objectDidChange: ObservableObjectPublisher { get }
var selected: Bool { get set }
}
class Model: ModelType {
let objectDidChange = ObservableObjectPublisher()
var selected = false {
didSet {
objectDidChange.send()
}
}
}
class ViewModel<Model:ModelType>: ObservableObject {
var selected = false {
didSet {
model.selected = selected
}
}
func add(model: Model) {
self.model = model
cancellable = model.objectDidChange.sink(receiveValue: { _ in
self.update()
})
}
private var model: Model! = nil
private var cancellable: AnyCancellable? = nil
func update() {
// Crash log: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
print("update \(model.selected)")
}
}
Short version: require AnyObject for ModelType.
Long version:
You're trying to read from self.model while you're in the middle of setting self.model. When you say "If the "update" code is moved into the "receiveValue" closure, the error will not occur," this isn't quite correct. I expect what you mean is you wrote this:
cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(model.selected)")
})
And that worked, but that's completely different code. model in this case is the local variable, not the property self.model. You'll get the same crash if you write it this way:
cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(self.model.selected)")
})
The path that gets you here is:
ViewModel.selected.didSet
WRITE to Model.selected <---
Model.selected.didSet
(observer-closure)
ViewModel.update
READ from ViewModel.model <---
This is a read and write to the same value, and that violates exclusive access. Note that the "value" in question is "the entire ViewModel value," not ViewModel.selected. You can show this by changing the update function to:
print("update \(model!)")
You'll get the same crash.
So why does this work when you take out the generic? Because this particularly strict version of exclusivity only applies to value types (like structs). It doesn't apply to classes. So when this is concrete, Swift knows viewModel is a class, and that's ok. (The why behind this difference a bit complex, so I suggest reading the proposal that explains it.)
When you make this generic, Swift has to be very cautious. It doesn't know that Model is a class, so it applies stricter rules. You can fix this by promising that it's a class:
protocol ModelType: AnyObject { ... }
I'm having trouble with a BehaviorRelay that has a protocol type and using it on concrete types. Here's my code:
protocol Item {
var title: { get }
}
struct Can: Item {
let title = "Can"
}
let canRelay = BehaviorRelay<Can?>(value: nil)
func handle(item: BehaviorRelay<Item?>) {
// do something with item here
}
handle(item: canRelay) // can't do this?
I assumed I would be able to call handle(item:) but it's not the case because the arguments don't match. I get that they don't match, but Can is a type of Item so shouldn't this be possible?
Can may be a subtype of Item, but BehaviorRelay<Can> is not a subtype of BehaviorRelay<Item>.
Also, you should not be passing BehaviorRelays around in code. Pass Observables instead.
Once you know these two rules, you end up with:
func handle(item: Observable<Item?>) {
// do something with item here
}
handle(item: canRelay.map { $0 })
This is a simplified form of some swift3 code:
class GenericListViewModel<CellViewModel> {
let cells: [CellViewModel]
required init(cells: [CellViewModel]) {
self.cells = cells
}
}
class ViewController<CellViewModel, ListViewModel: GenericListViewModel<CellViewModel>> {
var viewModel: ListViewModel
init(cellViewModels: [CellViewModel]) {
viewModel = ListViewModel(cells: cellViewModels)
}
}
The compiler crashes with the following error:
While emitting IR SIL function #_TFC4Xxxx14ViewControllercfT14cellViewModelsGSax__GS0_xq__ for 'init'
at /.../GenericStuff.swift:22:5
Am is missing something, or is this a Swift compiler bug?
Edit:
I reported this here https://bugs.swift.org/browse/SR-3315 and it looks like it's fixed in current swift master branch.
You're pushing the system too hard with inheritance. Generics based on subclasses of other generics tends to break the compiler's brain, and is usually not really what you meant anyway. (That said: there is never an excuse for the compiler crashing, so you should absolutely open a bugreport.)
Do you really mean to subclass GenericListViewModel and then parameterize ViewController on that precise subclass? This seems very over-complicated and I'm not seeing how you would get any actual value out of it (since you can't rely on any additional methods added to your subclasses, and you already have dynamic dispatch). You're using both subclasses and generics to solve the same problem.
What you likely mean is that there's a CellViewModel and you want GenericListViewModel<CellViewModel> to wrap that, and aren't thinking about subclasses at all.
So, assuming you don't really mean to parameterize this specifically, let inheritance do its job. ListViewModel should be typealias, not a type parameter:
class ViewController<CellViewModel> {
typealias ListViewModel = GenericListViewModel<CellViewModel>
var viewModel: ListViewModel
init(cellViewModels: [CellViewModel]) {
viewModel = ListViewModel(cells: cellViewModels)
}
}
Now it's fine. That said, do you really need the view model to be a reference type? View models often don't need identity themselves (unless you're observing them with KVO). They may wrap a reference type, but as an adapter, a value type is often fine. Assuming this is true for you, then this can and should be simplified to a struct:
struct GenericListViewModel<CellViewModel> {
let cells: [CellViewModel]
}
class ViewController<CellViewModel> {
typealias ListViewModel = GenericListViewModel<CellViewModel>
var viewModel: ListViewModel
init(cellViewModels: [CellViewModel]) {
viewModel = ListViewModel(cells: cellViewModels)
}
}
To your goals of "custom logic like filtering the model, or keeping some other state specific to each controller," I would be very careful of using subclasses for this. It sounds like you're tempted to mix too much functionality into a single type. First, think about how you'd call your code the way you're thinking about it. ListViewModel isn't constrained by the init call, so you can't use type-inference. You'll have to initialize it like:
let vc: ViewController<SomeCellModel, GenericListViewModelSubclass<SomeCellModel>> = ViewController(cells: cells)
That's pretty hideous and is fighting all the things Swift wants to help you with. Since you want to be able to pass in the ListViewModel type, let's just pass it in. This is what protocols are for, not classes.
protocol CellViewModelProviding {
associatedtype CellViewModel
var cells: [CellViewModel] { get }
}
class ViewController<ListViewModel: CellViewModelProviding> {
var viewModel: ListViewModel
init(listViewModel: ListViewModel) {
viewModel = listViewModel
}
}
Now we can create different providers.
// A more standard name for your GenericListViewModel
struct AnyListViewModel<CellViewModel>: CellViewModelProviding {
let cells: [CellViewModel]
}
struct FilteredListViewModel<CellViewModel>: CellViewModelProviding {
var cells: [CellViewModel] {
return unfilteredCells.filter(predicate)
}
var unfilteredCells: [CellViewModel]
var predicate: (CellViewModel) -> Bool
}
Now we can use it with:
let vc = ViewController(listViewModel: AnyListViewModel(cells: [1,2,3]))
let vc2 = ViewController(listViewModel: FilteredListViewModel(unfilteredCells: [1,2,3],
predicate: { $0 % 2 == 0 }))
So that's pretty nice, but we could do better. It's kind of annoying to have to wrap our cells up in an AnyListViewModel in the normal case. We could probably create a factory method to get around this, but yuck. The better answer is to make use of the power of AnyListViewModel to be a type eraser. This is going to get a little more advanced, so if you're happy with the above solution, you can stop, but let's walk through it because it's really powerful and flexible if you need it.
First, we convert AnyListViewModel into a full type eraser that can accept either another view list model, or just an array.
struct AnyListViewModel<CellViewModel>: CellViewModelProviding {
private let _cells: () -> [CellViewModel]
var cells: [CellViewModel] { return _cells() }
init(cells: [CellViewModel]) {
_cells = { cells }
}
init<ListViewModel: CellViewModelProviding>(_ listViewModel: ListViewModel)
where ListViewModel.CellViewModel == CellViewModel {
_cells = { listViewModel.cells }
}
}
Now ViewController doesn't have to care what kind of ListViewModel is passed. It can turn anything into an AnyListViewModel and work with that.
class ViewController<CellViewModel> {
var viewModel: AnyListViewModel<CellViewModel>
init<ListViewModel: CellViewModelProviding>(listViewModel: ListViewModel)
where ListViewModel.CellViewModel == CellViewModel {
viewModel = AnyListViewModel(listViewModel)
}
init(cells: [CellViewModel]) {
viewModel = AnyListViewModel(cells: cells)
}
}
OK, that's cool, but it's not a huge improvement. Well, let's rebuild FilteredListViewModel and see what that gets us.
struct FilteredListViewModel<CellViewModel>: CellViewModelProviding {
var cells: [CellViewModel] {
return listViewModel.cells.filter(predicate)
}
private var listViewModel: AnyListViewModel<CellViewModel>
var predicate: (CellViewModel) -> Bool
// We can lift any other listViewModel
init<ListViewModel: CellViewModelProviding>(filtering listViewModel: ListViewModel,
withPredicate predicate: #escaping (CellViewModel) -> Bool)
where ListViewModel.CellViewModel == CellViewModel {
self.listViewModel = AnyListViewModel(listViewModel)
self.predicate = predicate
}
// Or, just for convenience, we can handle the simple [cell] case
init(filtering cells: [CellViewModel], withPredicate predicate: #escaping (CellViewModel) -> Bool) {
self.init(filtering: AnyListViewModel(cells: cells), withPredicate: predicate)
}
}
This is where things get powerful. We've said FilteredListViewModel can take some cells and filter them, sure. But it can also filter any other view list model.
let someList = AnyListViewModel(cells: [1,2,3])
let evenList = FilteredListViewModel(filtering: someList, withPredicate: { $0 % 2 == 0 })
So now you can chain things together. You could glue together filtering with sorting or something that modified the cells or whatever. You don't need one super-specialized subclass that does everything you need. You can click together simpler pieces to build complex solutions.