尝试从 WWDC Session 重新创建 SwiftUI-based 应用程序时出现问题

Issues While Trying To Recreate SwiftUI-based App From WWDC Session

我正在尝试重新创建 Session 204 中演示的 SwiftUI 项目,但 运行 遇到了一些特殊问题。

我是在观看 session 时写下这篇文章的:https://developer.apple.com/videos/play/wwdc2019/204

这是我的代码-

ContentView.swift:

import SwiftUI
struct ContentView : View {
    @ObjectBinding var store = RoomStore()

    var body: some View {
        NavigationView {
            List {
                Section {
                    Button(action: addRoom) {
                        Text("Add Room")
                    }
                }

                Section {

                    ForEach(store.rooms) { room in //Error: Cannot convert value of type '(Room) -> RoomCell' to expected argument type '(_) -> _'
                        RoomCell(room: room)
                    }
                    .onDelete(perform: delete)
                    .onMove(perform: move)
                }
            }
            .navigationBarTitle(Text("Rooms") )
            .NavigationBarItems(trailing: EditButton())
            .listStyle(.grouped)
        }
    }

    func addRoom() {
        store.rooms.append(Room(name: "Hall 2", capacity: 2000))
    }
    func delete(at offsets: IndexSet) {
        store.rooms.remove(atOffsets: offsets) //Error: Incorrect argument label in call (have 'atOffsets:', expected 'at:')
    }
    func move(from source: IndexSet, to destination: Int) {
        store.rooms.move(fromOffsets: source, toOffset: destination) //Error: Value of type '[Room]' has no member 'move'; did you mean 'remove'?
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(store: RoomStore(rooms: testData))
            ContentView(store: RoomStore(rooms: testData))
            .environment(\.sizeCategory, .extraExtraExtraLarge)
            ContentView(store: RoomStore(rooms: testData))
            .environment(\.colorScheme, .dark)
            ContentView(store: RoomStore(rooms: testData))
            .environment(\.layoutDirection, .rightToLeft)
            .environment(\.locale, Locale(identifier: "ar"))
        }
    }
}
#endif
struct RoomCell : View {
    let room: Room
    var body: some View {
        return NavigationButton(destination: RoomDetail(room: room) )
        {
            Image(room.thumbnailName)
            .cornerRadius(8)
            VStack(alignment: .leading) {
                Text (room.name)
                Text ("\(room.capacity) peopje")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
    }
}

Room.swift:

import SwiftUI

struct Room {
    var id = UUID()
    var name: String
    var capacity: Int
    var hasVideo: Bool = false
    var imageName: String { return name }
    var thumbnailName: String { return name + "Thumb" }

}

#if DEBUG

let testData = [
    Room(name: "Observation Deck", capacity: 6, hasVideo: true),
    Room(name: "Executive Suite", capacity: 8, hasVideo: false),
    Room(name: "Charter Jet", capacity: 16, hasVideo: true),
    Room(name: "Dungeon", capacity: 10, hasVideo: true),
    Room(name: "Panorama", capacity: 12, hasVideo: false),
    Room(name: "Oceanfront", capacity: 8, hasVideo: false),
    Room(name: "Rainbow Room", capacity: 10, hasVideo: true),
    Room(name: "Pastoral", capacity: 7, hasVideo: false),
    Room(name: "Elephant Room", capacity: 1, hasVideo: false),

]

#endif

RoomDetail.swift:

import SwiftUI
struct RoomDetail : View {
    let room: Room
    @State private var zoomed = false
    var body: some View { //Error: Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
        ZStack(alignment: .topLeading) {
            Image(room.imageName )
                .resizable()
                .aspectRatio(contentMode: zoomed ? .fill : .fit)
                .navigationBarTitle(Text(room.name), displayMode:
                    .inline)
                .tapAction { withAnimation(.basic(duration: 2)) {
                    self.zoomed.toggle() } }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight:
                0, maxHeight: .infinity)
            if room.hasVideo && !zoomed {
                Image(systemName: "video. fill")
                    .font(.title)
                    .padding(.all)
                    .transition(.move(edge: .leading) )
            }
        }
}

#if DEBUG
struct RoomDetail_Previews : PreviewProvider {
    static var previews: some View {
        Group {
            NavigationView { RoomDetail(room: testData[0]) }
            NavigationView { RoomDetail(room: testData[1]) }
        }
}
}
#endif

RoomStore.swift:

import SwiftUI
import Combine

class RoomStore : BindableObject {
    var rooms: [Room] {
        didSet { didChange.send(Void()) } //Solved
    }
    init(rooms: [Room] = []) {
        self.rooms = rooms
    }
    var didChange = PassthroughSubject<Void, Never>()
}

错误消息包含在上下文中,作为注释,在上面的代码中。

你试过通过 Void() 吗?

class RoomStore : BindableObject {
    var rooms: [Room] {
        didSet { didChange.send(Void()) } 
    }
    init(rooms: [Room] = []) {
        self.rooms = rooms
    }
    var didChange = PassthroughSubject<Void, Never>()
}

根据 https://developer.apple.com/tutorials/swiftui/handling-user-input 第 4 节第 2 步,PassthroughSubject 应采用您要绑定的 class 的类型,而不是 Void。所以它似乎更正确(并且有效)。

var didChange = PassthroughSubject<RoomStore, Never>()

至于删除和移动功能,因为 Swift 5.1 数组既没有移动方法也没有 remove(atOffsets:) 方法,我只能假设他们忘记删除数组的一些自定义扩展。我也找不到在 Swift Evolution 上提到这些功能。

希望他们只是忘了告诉我们他们将在以后的版本中推出。

:) 泰奥

要在 List 中使用 Room,您必须实施 Identifiable 协议。我也忘了它和 SwiftUI 错误消息:

Cannot convert value of type '(Room) -> RoomCell' to expected argument type '(_) -> _'

没有帮助。

import SwiftUI

struct Room: Identifiable {
    let id = UUID() 
    ...
}

要删除和移动你可以使用这样的东西:

func delete(at offsets: IndexSet) {
    offsets.sorted { [=10=] >  }.forEach { store.rooms.remove(at: [=10=]) }
}

func move(from source: IndexSet, to destination: Int) {
    source.sorted { [=10=] >  }.forEach { store.rooms.insert(store.rooms.remove(at: [=10=]), at: destination) }
}

对于此错误://错误:无法将类型“(Room) -> RoomCell”的值转换为预期的参数类型“(_) -> _” 像这样为您的列表模型实施可识别协议,

struct Room: Identifiable

对于这个://错误:调用中的参数标签不正确(有 'atOffsets:',预期 'at:') 我认为这不是你的问题:) 但你可以使用这样的东西,

guard let index = Array(offset).first else { return }
store.rooms.remove(at: index)

对于这个://错误:类型“[Room]”的值没有成员'move';你是说 'remove'? 和以前一样,你可以使用那段代码移动

guard let sourceIndex = Array(source).first else { return }
store.rooms.insert(roomStore.rooms.remove(at: sourceIndex), at: destination)

您可以查看完整的源代码, https://github.com/ilyadaberdil/iOS-Samples/tree/master/SwiftUI-Sample

Xcode11测试版4

The BindableObject protocol’s requirement is now willChange instead of didChange, and should now be sent before the object changes rather than after it changes. This change allows for improved coalescing of change notifications.

class RoomStore: BindableObject {

    var rooms: [Room] {
        didSet { willChange.send() }
    }

    init(rooms: [Room] = []) {
        self.rooms = rooms
    }

    var willChange = PassthroughSubject<Void, Never>()
}

要删除和移动,请使用以下代码。

func delete(at offsets: IndexSet) {
    store.rooms.remove(atOffsets: offsets)
}

func move(from source: IndexSet, to destination: Int) {
    store.rooms.move(fromOffsets: source, toOffset: destination)
}

Rooms 必须是 @Published

class RoomStore: ObservableObject {
    @Published var rooms: [Room] {
        didSet {
            willChange.send()
        }
    }

    init(rooms: [Room] = []) {
        self.rooms = rooms
    }

   var willChange = PassthroughSubject<Void, Never>()
}