I'm trying to use "objectWillChange.send()" in an protocol extension but it's not working, any idea why? - protocols

I've a SwiftUI navigation view showing a list of players. I designed the view model protocols as follows.
protocol PlayerListStateProviding: ObservableObject {
var players: [PlayerModel] { get set }
}
protocol PlayerListDeleting {
var moc: NSManagedObjectContext { get set }
func delete(at indexSet: IndexSet)
}
extension PlayerListDeleting where Self: PlayerListStateProviding {
func delete(at indexSet: IndexSet) {
moc.delete(players.remove(at: indexSet.first!))
objectWillChange.send() // this doesn't compile with the following error "Value of type 'Self.ObjectWillChangePublisher' has no member 'send'"
}
}
I'm not sure what this error is and how to avoid it. However when I remove the extension and create concrete class, I could send the signal with no problem.

To use default observable object publisher in protocol you should limit it to corresponding type (because it is in extension to ObservableObject), as in
extension PlayerListDeleting where Self: PlayerListStateProviding,
Self.ObjectWillChangePublisher == ObservableObjectPublisher {
func delete(at indexSet: IndexSet) {
moc.delete(players.remove(at: indexSet.first!))
objectWillChange.send()
}
}

Related

Converting Key-Value Observation in AppKit/UIKit to Combine and SwiftUI

I'm having trouble wrapping my head around how to use Combine in SwiftUI. I'm accustomed to using key-value observation in AppKit an UIKit because view controllers don't need to know about each other and can just react to some global objects that help determine state.
For example, in an AppKit/UIKit app, I would create a global state object like this:
//Global State file
#objc class AppState: NSObject {
#objc dynamic var project: Project?
}
//Create an instance I can access anywhere in my app
let app = AppState()
Then in a view controller, I can get notified of any changes to my app-wide project instance and react accordingly:
//View Controller
class MyViewController: NSViewController{
var observerProject: NSKeyValueObservation?
override func viewDidLoad() {
observerProject = app.observe(\.project) { object, change in
self.refreshData()
}
}
func refreshData(){
//Query my persistent store and update my UI
}
}
What is the Combine/SwiftUI analog to this?
Do I need to create a Publisher and then listen to my global object changes? If so, how do I make my Core Data #FetchRequest (whose predicate includes my global Project object) respond in real-time?
I've done things the old way for so long that this transition to SwiftUI/Combine is rather confusing to me. 🙂
#FetchRequest doesn't work well with a dynamic predicate (There are some workarounds in SO) you will have to use the "old school" NSFetchedResultsController for that and put it into an ObservableObject. Here is a video with a sample. It is a lot of setup and code.
import SwiftUI
import Combine
class AppState: ObservableObject {
static let shared = AppState()
#Published var project: String?
//Just to mimic updates
var count = 0
private init() {
//Mimic getting updates to project
Timer.scheduledTimer(withTimeInterval: 2, repeats: true){ timer in
print("Timer")
self.project = self.count.description
self.count += 1
if self.count >= 15{
timer.invalidate()
}
}
}
}
class MyViewModel: ObservableObject{
#Published var refreshedData: String = "init"
let appState: AppState = AppState.shared
var projectCancellable: AnyCancellable?
init() {
projectCancellable = appState.$project.sink(receiveValue: {
value in
print(value ?? "nil")
self.refreshData()
})
}
func refreshData() {
refreshedData = Int.random(in: 0...100).description
}
}
struct MyView: View {
#StateObject var vm: MyViewModel = MyViewModel()
var body: some View {
VStack{
Text(vm.refreshedData)
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView()
}
}

How to define a protocol to include a property with #Published property wrapper

When using #Published property wrapper following current SwiftUI syntax, it seems very hard to define a protocol that includes a property with #Published, or I definitely need help :)
As I'm implementing dependency injection between a View and it's ViewModel, I need to define a ViewModelProtocol so to inject mock data to preview easily.
This is what I first tried,
protocol PersonViewModelProtocol {
#Published var person: Person
}
I get "Property 'person' declared inside a protocol cannot have a wrapper".
Then I tried this,
protocol PersonViewModelProtocol {
var $person: Published
}
Obviously didn't work because '$' is reserved.
I'm hoping a way to put a protocol between View and it's ViewModel and also leveraging the elegant #Published syntax. Thanks a lot.
You have to be explicit and describe all synthetized properties:
protocol WelcomeViewModel {
var person: Person { get }
var personPublished: Published<Person> { get }
var personPublisher: Published<Person>.Publisher { get }
}
class ViewModel: ObservableObject {
#Published var person: Person = Person()
var personPublished: Published<Person> { _person }
var personPublisher: Published<Person>.Publisher { $person }
}
My MVVM approach:
// MARK: View
struct ContentView<ViewModel: ContentViewModel>: View {
#ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text(viewModel.name)
TextField("", text: $viewModel.name)
.border(Color.black)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: ContentViewModelMock())
}
}
// MARK: View model
protocol ContentViewModel: ObservableObject {
var name: String { get set }
}
final class ContentViewModelImpl: ContentViewModel {
#Published var name = ""
}
final class ContentViewModelMock: ContentViewModel {
var name: String = "Test"
}
How it works:
ViewModel protocol inherits ObservableObject, so View will subscribe to ViewModel changes
property name has getter and setter, so we can use it as Binding
when View changes name property (via TextField) then View is notified about changes via #Published property in ViewModel (and UI is updated)
create View with either real implementation or mock depending on your needs
Possible downside: View has to be generic.
A workaround my coworker came up with is to use a base class that declares the property wrappers, then inherit it in the protocol. It still requires inheriting it in your class that conforms to the protocol as well, but looks clean and works nicely.
class MyPublishedProperties {
#Published var publishedProperty = "Hello"
}
protocol MyProtocol: MyPublishedProperties {
func changePublishedPropertyValue(newValue: String)
}
class MyClass: MyPublishedProperties, MyProtocol {
changePublishedPropertyValue(newValue: String) {
publishedProperty = newValue
}
}
Then in implementation:
class MyViewModel {
let myClass = MyClass()
myClass.$publishedProperty.sink { string in
print(string)
}
myClass.changePublishedPropertyValue("World")
}
// prints:
// "Hello"
// "World"
This is how I suppose it should be done:
public protocol MyProtocol {
var _person: Published<Person> { get set }
}
class MyClass: MyProtocol, ObservableObject {
#Published var person: Person
public init(person: Published<Person>) {
self._person = person
}
}
Although the compiler seems to sort of like it (the "type" part at least), there is a mismatch in the property's access control between the class and the protocol (https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html). I tried different combinations: private, public, internal, fileprivate. But none worked. Might be a bug? Or missing functionality?
Until 5.2 we don't have support for property wrapper. So it's necessary expose manually the publisher property.
protocol PersonViewModelProtocol {
var personPublisher: Published<Person>.Publisher { get }
}
class ConcretePersonViewModelProtocol: PersonViewModelProtocol {
#Published private var person: Person
// Exposing manually the person publisher
var personPublisher: Published<Person>.Publisher { $person }
init(person: Person) {
self.person = person
}
func changePersonName(name: String) {
person.name = name
}
}
final class PersonDetailViewController: UIViewController {
private let viewModel = ConcretePersonViewModelProtocol(person: Person(name: "Joao da Silva", age: 60))
private var cancellables: Set<AnyCancellable> = []
func bind() {
viewModel.personPublisher
.receive(on: DispatchQueue.main)
.sink { person in
print(person.name)
}
.store(in: &cancellables)
viewModel.changePersonName(name: "Joao dos Santos")
}
}
We've encountered this as well. As of Catalina beta7, there doesn't seem to be any workaround, so our solution is to add in a conformance via an extension like so:
struct IntView : View {
#Binding var intValue: Int
var body: some View {
Stepper("My Int!", value: $intValue)
}
}
protocol IntBindingContainer {
var intValue$: Binding<Int> { get }
}
extension IntView : IntBindingContainer {
var intValue$: Binding<Int> { $intValue }
}
While this is a bit of extra ceremony, we can then add in functionality to all the IntBindingContainer implementations like so:
extension IntBindingContainer {
/// Reset the contained integer to zero
func resetToZero() {
intValue$.wrappedValue = 0
}
}
I came up with a fairly clean workaround by creating a generic ObservableValue class that you can include in your protocols.
I am unsure if there are any major drawbacks to this, but it allows me to easily create mock/injectable implementations of my protocol while still allowing use of published properties.
import Combine
class ObservableValue<T> {
#Published var value: T
init(_ value: T) {
self.value = value
}
}
protocol MyProtocol {
var name: ObservableValue<String> { get }
var age: ObservableValue<Int> { get }
}
class MyImplementation: MyProtocol {
var name: ObservableValue<String> = .init("bob")
var age: ObservableValue<Int> = .init(29)
}
class MyViewModel {
let myThing: MyProtocol = MyImplementation()
func doSomething() {
let myCancellable = myThing.age.$value
.receive(on: DispatchQueue.main)
.sink { val in
print(val)
}
}
}
Try this
import Combine
import SwiftUI
// MARK: - View Model
final class MyViewModel: ObservableObject {
#Published private(set) var value: Int = 0
func increment() {
value += 1
}
}
extension MyViewModel: MyViewViewModel { }
// MARK: - View
protocol MyViewViewModel: ObservableObject {
var value: Int { get }
func increment()
}
struct MyView<ViewModel: MyViewViewModel>: View {
#ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text("\(viewModel.value)")
Button("Increment") {
self.viewModel.increment()
}
}
}
}
I succeeded in just requiring the plain variable, and by adding the #Published in the fulfilling class:
final class CustomListModel: IsSelectionListModel, ObservableObject {
#Published var list: [IsSelectionListEntry]
init() {
self.list = []
}
...
protocol IsSelectionListModel {
var list: [IsSelectionListEntry] { get }
...

node.js serialize/deserialize custom objects to mongodb

I have the following architecture:
class Base {
constructor(initObj) { /* some init */}
// some methods
}
class Foo extends Base {
constructor(initObj) { super(initObj); }
// some methods
}
class Bar extends Base {
constructor(initObj) { super(initObj); }
// some methods
}
How can I serialize/deserialize an Array of Base into mongodb ?
For the moment, I save an attribute type on each object to know if its a Foo or a Bar and my Foo and Bar's constructor looks like that:
constructor(initObj) {
super(initObj);
if (initObj.fromMongo) this.restore(initObj)
else this.initialize(initObj)
this.type = 'bar'; // or baz according to the object
}
because as you can imagine, the creation from a saved object and from new data is not the same.
Does someone knows a less tricky way to realize these operations ?
In mongoose these things are done easily. But as far as you don't, I can suggest you such flow:
you have collection base in mongo, with field type, that specifies foo or bar instance
implement deserialize method in base
you implement serialize method in each child class
you use your methods, when having array of base objects
I would modify base class:
class Base {
constructor(initObj) { /* some init */}
serialize(model) {
throw new Error('not implemented')
}
deserialize(mongoModel) {
// very rude, just to catch the point
// most probably, you'll have to map object before creating new instance
if (mongoModel.type === 'Foo') return new Foo(mongoModel);
else return new Bar(mongoModel);
}
}
I'll write approximate implementation of these methods:
class Foo extends Base {
constructor(initObj) { super(initObj); }
serialize(model) {
// again very rude, it dependes on your logic
return JSON.stringify(model);
}
}
And then, assuming you have array of Base objects you can easily map them:
const mongoBaseModels = baseObjects.map(el => el.serialize(el))

Swift passing structs between view controllers?

My app has the user log into their account. Then, the app accesses Parse.com to retrieve all of their information. That information is stored in a struct. I want to have that struct passed between view controllers so it can be accessed at any time in the app. Everything I've tried gives me errors. I can't declare it as an optional, or set up an identical struct in the next class. What is the solution to this?
Use the following code to pass struct between ViewControllers
//FirstViewController.swift
struct GlobalStruct
{
var details:String;
init()
{
details = "global struct";
}
};
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonClicked()
{
let secondViewController = self.storyboard.instantiateViewControllerWithIdentifier("secondview") as SecondViewController
var passData = GlobalStruct();
passData.details = "Secret Information :)";
secondViewController.setStructDataReference(passData); //passStruct
self.navigationController.pushViewController(secondViewController, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
//SecondViewController.swift
class SecondViewController:UIViewController
{
var structData:GlobalStruct;
override func viewDidLoad()
{
println("struct data = \(self.structData.details)");
}
func setStructDataReference(structDataReference:GlobalStruct)
{
self.structData = structDataReference;
}
init(coder aDecoder: NSCoder!)
{
self.structData = GlobalStruct();
super.init(coder: aDecoder);
}
}
I would create empty arrays [Struct] on both controllers.
Then in whatever action you perform before performSegue you should do:
self.arrayinmaincontroller.insert(the struct, at:0)
and pass it in func prepare(for segue: UIStoryboardSegue, sender: Any?)

Retrieving an Enum through a class and its descendants

I have a class that I've defined, and I have a number of child classes derived from it. The parent class has an enum (let's call it 'Barf'). Each descendant ALSO has an enum with the same name but not the same values. What I'm trying to figure out how to do is write a method in the ancestor class that gets the version of Barf for the actual class of the instantiated object. So if I create an instance of Ancestor, I'd like to have this method process the entries for Ancestor.Barf . If I create an instance of one of the child classes of Ancestor, I'd like to have the method process Childx.Barf values.
Obviously this is going to be a Reflection solution, but my reflection skills are pretty sparse. Any help?
Just for the fun of it, here is a possible approach:
public class Ancestor {
public enum Caffeine {
Tea,
Coffee
}
public void ProcessValues() {
var type = GetType();
var nestedEnums = from t in type.GetNestedTypes()
where t.IsEnum
select t;
var nestedEnum = nestedEnums.Single();
foreach(var val in Enum.GetValues(nestedEnum)) {
Console.WriteLine("Drinking {0}", val);
}
}
}
public class Descendant : Ancestor {
public new enum Caffeine {
Jolt,
RedBull
}
}
// The following prints:
// Drinking Jolt
// Drinking RedBull
Ancestor x = new Descendant();
x.ProcessValues();
Of course, you could achieve the same thing using polymorphism:
public class Ancestor {
public enum Caffeine {
Tea,
Coffee
}
protected virtual Type GetNestedEnum() {
return typeof(Ancestor.Caffeine);
}
public void ProcessValues() {
var nestedEnum = GetNestedEnum();
foreach(var val in Enum.GetValues(nestedEnum)) {
Console.WriteLine("Drinking {0}", val);
}
}
}
public class Descendant : Ancestor {
public new enum Caffeine {
Jolt,
RedBull
}
protected override Type GetNestedEnum() {
return typeof(Descendant.Caffeine);
}
}
As Justin Morgan has pointed out however, having the need for such a construct may be an indication of an underlying design issue in your code.

Resources