2

I have an application in which I use SwiftUI and Core Data. In the Core Data datamodel, I have an Entity, Record, which has various attributes, two of which are of a custom class type, who's purpose is to act as a wrapper for an array. These classes both contain a property which is an array of another custom class type (Array<[Class]>). Some of properties in the definitions of the aforementioned class types are prefixed with the @Published property wrapper, so that the view will update when these properties change, along with the context. Rather unfortunately, this does not seem to be the case — and I can't work out why! I have tried to work around this by having a custom delegate to manually call objectWillChange.send(), but this doesn't feel very clean, nor does it register changes with the context.

The application can be found here, the relevant data model classes are in this directory, and the relevant view classes are ContentView.swift and RecordDetailView.swift (I have commented the [SwiftUIView]_Previews structs because I'm not quite sure how to get the previews to work with the Core Data container). I won't post many code-snippets here, as they are somewhat dependent on each other and there is too much to form a concise question.

Below is the definition of one of these "wrapper" classes:

import Foundation

@objc(Nodes)
public class Nodes: NSObject, NSSecureCoding, ObservableObject {
    public static var supportsSecureCoding: Bool = true

    @Published public var nodes = [Node]()

    public enum Keys: String {
        case nodes = "nodes"
    }

    override init() {
        super.init()
    }

    init(nodes: [Node]) {
        super.init()
        self.nodes = nodes
        for node in nodes {
            node.delegates.append(self)
        }
    }

    public func encode(with coder: NSCoder) {
        coder.encode(nodes, forKey: Keys.nodes.rawValue)
    }

    public required convenience init?(coder: NSCoder) {
        let nodes = coder.decodeObject(forKey: Keys.nodes.rawValue) as! [Node]
        self.init(nodes: nodes)
    }
}

extension Nodes: NodeDelegate {
    func didUpdateNode(node: Node) {
        objectWillChange.send()
    }
}

And below is the definition of the Array type:

import Foundation

@objc(Node)
public class Node: NSObject, NSSecureCoding, ObservableObject, Identifiable {
    public static var supportsSecureCoding: Bool = true

    public var id = UUID()
    var delegates: [NodeDelegate] = [NodeDelegate]() {
        didSet {
            delegates.forEach { $0.didUpdateNode(node: self) }
        }
    }
    @Published private var x = "0" {
        didSet {
            delegates.forEach { $0.didUpdateNode(node: self) }
        }
    }
    @Published private var y = "0" {
        didSet {
            delegates.forEach { $0.didUpdateNode(node: self) }
        }
    }
    var xDouble: Double {
        set {
            x = String(newValue)
        }
        get {
            if let x = Double(x) {
                return x
            } else {
                return 0
            }
        }
    }
    var yDouble: Double {
        set {
            y = String(newValue)
        }
        get {
            if let y = Double(y) {
                return y
            } else {
                return 0
            }
        }
    }
    var xString: String {
        get {
            x
        }
        set {
            x = newValue
        }
    }
    var yString: String {
        get {
            y
        }
        set {
            y = newValue
        }
       }

    public enum Keys: String {
        case x = "x"
        case y = "y"
        case delegates = "delegates"
    }

    override init() {
        super.init()
    }

    convenience init(x: String, y: String) {
        self.init()
        xString = x
        yString = y
    }

    convenience init(x: Double, y: Double) {
        self.init()
        self.xDouble = x
        self.yDouble = y
    }

    public func encode(with coder: NSCoder) {
        coder.encode(x, forKey: Keys.x.rawValue)
        coder.encode(y, forKey: Keys.y.rawValue)
        coder.encode(delegates, forKey: Keys.delegates.rawValue)
    }

    public required convenience init?(coder: NSCoder) {
        let x = coder.decodeObject(forKey: Keys.x.rawValue) as! String
        let y = coder.decodeObject(forKey: Keys.y.rawValue) as! String
        let delegates = coder.decodeObject(forKey: Keys.delegates.rawValue) as! [NodeDelegate]
        self.init(x: x, y: y)
        self.delegates = delegates
    }
}

On another note, there are also several really horrible UI glitches which I do not understand, for example, sometimes when tapping on a RecordThumbnailView from a ContentView it will do nothing, but then when tapping on a different RecordThumbnailView it will suddenly push all of the RecordDetailViews which correspond to all of the unresponsive RecordThumbnailViews. In addition, when performing this same segue, the ChartViewControllerView will jolt upwards very quickly, and will be hidden behind the navigation bar.

If you know of any ways to overcome these issues, I'd appreciate the assistance — I've been staring at these problems for several days!

JacobCXDev
  • 417
  • 6
  • 16
  • 1
    I had a look at your project. For me it looks like a mix of different contradicting principles. E.g. ObservableObject (Combine) but no ObservedObject counterpart in SwiftUI view, @Published variables with "didSet" calls. A lot of environmentObject modifiers also for direct child views. I suggest to clear the architecture in a more SwiftUI/Combine like way where a class implementing ObservableObject protocol has an ObservedObject counterpart in the SwiftUI view and EnvironmentObject will be used for global managers/services only and parent child hierarchies such as lists just pass the record. – berni Dec 06 '19 at 07:53
  • @berni Thanks for your feedback! Do you know of any examples I could use to help guide me? – JacobCXDev Dec 07 '19 at 10:15
  • Is this perhaps a duplicate of https://stackoverflow.com/questions/58357414/changes-to-swiftui-fetchrequest-not-triggering-view-refresh ? – Lord Zsolt Dec 09 '19 at 10:45

0 Answers0