Propertywrappers and Protocol Declaration

Force view that conforms to protocol to have a property wrapper in SwiftUI

Here are possible variants:

a) use stored property the same as in protocol (drawback: access via wrappedValue)

struct MyView: DismissableView {

var isPresented: Binding<Bool>

var body: some View {
Text(isPresented.wrappedValue ? "Presented" : "Not")
}
}

  1. use internal variable with wrapper to conform to protocol (drawback: needed wrapper in each confirmed view)
struct MyView: DismissableView {

var isPresented: Binding<Bool> {
get { _presented }
set { _presented = newValue }
}

@Binding var presented: Bool

// optional init if needed to have MyView(isPresented: Binding<Bool>) interface

// init(isPresented: Binding<Bool>) {
// self._presented = isPresented
// }

var body: some View {
Text(presented ? "Presented" : "Not")
}
}

External usage in both cases is same.

Note: the @Binding var isPresented: Bool property wrapper being unwrapped creates two properties (see below) that is why you cannot confirm it directly

var isPresented: Bool
var _isPresented: Binding<Bool>

How to define a protocol as a type for a @ObservedObject property?

Wrappers and stored properties are not allowed in swift protocols and extensions, at least for now. So I would go with the following approach mixing protocols, generics and classes... (all compilable and tested with Xcode 11.2 / iOS 13.2)

// base model protocol
protocol ItemViewModel: ObservableObject {
var title: String { get set }

func save()
func delete()
}

// generic view based on protocol
struct ItemView<Model>: View where Model: ItemViewModel {
@ObservedObject var viewModel: Model

var body: some View {
VStack {
TextField("Item Title", text: $viewModel.title)
Button("Save") { self.viewModel.save() }
}
}
}

// extension with default implementations
extension ItemViewModel {

var title: String {
get { "Some default Title" }
set { }
}

func save() {
// some default behaviour
}

func delete() {
// some default behaviour
}
}

// concrete implementor
class SomeItemModel: ItemViewModel {
@Published var title: String

init(_ title: String) {
self.title = title
}
}

// testing view
struct TestItemView: View {
var body: some View {
ItemView(viewModel: SomeItemModel("test"))
}
}

Swift - How to use KVO's 'observe' method on protocol

You can declare a function and apply the generic constraint to it.

In Swift 5.7 you can simply do:

func observeViewController(_ vc: some MyProtocol) {
vc.observe(\.observeMe, options: [.old, .new], changeHandler: { object, change in
})
}

Then you can call it:

let viewController = UIViewController()
if let observableViewController = viewController as? MyProtocol {
observeViewController(viewController)
}

Prior to Swift 5.7, some MyProtocol is not supported and you need to use type erasure:

class AnyMyProtocol: MyProtocol & UIViewController {
var observeMe: Bool

private var base: MyProtocol

init(_ base: MyProtocol) {
self.base = base
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

func observeViewController(_ vc: MyProtocol) {
vc.observe(\.observeMe, options: [.old, .new], changeHandler: { object, change in
})
}

And call it like so:

let viewController = UIViewController()
observeViewController(vc: AnyMyProtocol(base: observableViewController))

By the way, you will receive a warning reading:

Passing reference to non-'@objc dynamic' property 'observeMe' to KVO method 'observe(_:options:changeHandler:)' may lead to unexpected behavior or runtime trap

But in your case you can't declare it as @objc because an @objc protocol can't inherit from a non-protocol type.



Related Topics



Leave a reply



Submit