符合SwiftUI中的RandomAccessCollection时下标范围超出索引

时间:2020-09-15 07:25:20

标签: ios arrays swift swiftui

我创建了一个类,并向ObservableObject进行了确认,并且RandomAccessCollection将该类视为Array的自定义实现,但我不需要将其作为结构。我需要上课。

我尝试通过调用名为append的自定义函数将一个项目添加到类的内容中,并且效果很好,但是当我删除它时,由于某种原因它在subscript崩溃了,我试图进行跟踪它似乎每次删除时都会遍历所有包含已删除项目的内容,然后崩溃。所以我不太确定发生了什么。这是一段具有可复制代码的代码(只需复制并粘贴)

如果我将代码更改为struct,则可以100%正常工作,但我需要将其作为类。

Test.swift

import Foundation

public class Test<Element : Hashable>: ObservableObject{
    @Published fileprivate var contents:[Element] = []
    
    public var count:Int {
        return self.contents.count
    }
    
    init() {
        
    }
}

public extension Test {
    func append(_ newElement: Element) {
        self.contents.append(newElement)
    }
    
    func remove(_ at: Int) {
        self.contents.remove(at: at)
    }
}

extension Test : Collection, RandomAccessCollection {
    public typealias Index = Int
    public typealias Indices = CountableRange<Int>
    
    public var startIndex: Int {
        return self.contents.startIndex
    }
    
    public var endIndex: Int {
        return self.contents.endIndex
    }
    
    public subscript(position: Int) -> Element {
        get {
            return self.contents[position] // Crash occurs here after trying to remove
        }
    }
    public func index(after i: Int) -> Int {
        return self.contents.index(after: i)
    }
    
    public func index(before i: Int) -> Int {
        return self.contents.index(before: i)
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    @ObservedObject var test: Test<String> = Test<String>()
    @State var input: String = ""
    
    var body: some View {
        VStack {
            HStack {
                TextField("New Item", text: self.$input)
                
                Button("Add", action: {
                    if(!self.input.isEmpty) {
                        self.test.append(self.input)
                    }
                })
            }
            
            Button("Remove", action: {
                self.test.remove(0)
            }).disabled(self.test.count <= 0)
            
            List {
                ForEach(self.test, id:\.self) { n in
                    Text(n)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2 个答案:

答案 0 :(得分:1)

ForEach看不到数据的变化,因为self.test是参考。

这里是可能的解决方法(并且仍然具有Test的contents私有)。经过Xcode 11.7 / iOS 13.7的测试

    List {
        ForEach(Array(self.test), id:\.self) { n in
            Text(n)
        }
    }

答案 1 :(得分:0)

由于超出数组范围,因此可以将集合的返回值设为可选,也可以在初始化中提供默认值,以便在数组为空时可以返回。

@ObservedObject var test: Test<String> = Test<String>(emptyValue: "")

public class Test<Element : Hashable>: ObservableObject{
    private let empty: Element
    @Published fileprivate var contents:[Element] = []

    public var count:Int {
        return self.contents.count
    }
    init(emptyValue: Element) {
        empty = emptyValue
    }
}

extension Test {
    func append(_ newElement: Element) {
        self.contents.append(newElement)
    }

    func remove(_ at: Int) {
        self.contents.remove(at: at)
    }
}

extension Test : Collection, RandomAccessCollection {
    public typealias Index = Int
    public typealias Indices = CountableRange<Int>

    public var startIndex: Int {
        return self.contents.startIndex
    }

    public var endIndex: Int {
        return self.contents.endIndex
    }

    public subscript(position: Int) -> Element {
        get {
            position < self.contents.count ? self.contents[position] : empty
        }
    }
    public func index(after i: Int) -> Int {
        return self.contents.index(after: i)
    }

    public func index(before i: Int) -> Int {
        return self.contents.index(before: i)
    }
}