Получил "Фатальная ошибка: Индекс вне диапазона": показать индекс в элементе списка для swiftui - PullRequest
0 голосов
/ 20 февраля 2020

Обновлено: ошибка: тип '_' не имеет члена '1', если поместить в закрывающий список if (if! Self.showMarkedOnly || name.marked {}), почему?

Версия кода 4:

struct Name: Identifiable, Hashable {
    var id: String = UUID().uuidString
    var name: String
    var marked: Bool
    init(_ name: String, marked: Bool = false) { self.name = name; self.marked = marked }
}

struct TestView: View {
    @State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3", marked: true), Name("test4"), Name("test5", marked: true), Name("test6"), Name("test7"), Name("test8")]
    @State private var showMarkedOnly = false

    var body: some View {
        VStack{
            Toggle(isOn: $showMarkedOnly) { Text("show marked only") }
            List {
                ForEach(Array(zip(0..., list)), id: \.1.id) { index, name in
//                    if !self.showMarkedOnly || name.marked {
                        HStack {
                            Text("\(index)").foregroundColor(name.marked ? .red : .gray)
                            Spacer()
                            Text("\(name.name)")
                        }
                        .background(Color.gray.opacity(0.001))
                        .onTapGesture {
                            self.list.remove(at: index)
                        }
//                    }
                }
            }
        }
    }
}

=========

Обновлено: Я обнаружил проблему версии кода 2, я должен предоставить идентификатор для ForEach. И Код версии 2 обновлен.

Я нашел изящный способ показать индекс, он избегает self.list [index]. Но я обнаружил ошибку «У типа« _ »нет члена« 1 »» в некотором сложном коде.

Версия кода 3:

var body: some View {
        List {
            ForEach(Array(zip(0..., list)), id: \.1.id) { index, name in // a error occurs in some complex code: "Type '_' has no member '1'"
                HStack {
                    Text("\(index)")
                    Spacer()
                    Text("\(name.name)")
                }
                .background(Color.gray.opacity(0.001))
                .onTapGesture {
                    self.list.remove(at: index)
                }
            }
        }
    }

Я показываю список, и удалите предмет, который я нажал. Это код версии 1, он отлично работает. Когда я добавляю индекс в элемент списка с помощью индексов (Код veriosn 2), после нажатия появляется «Неустранимая ошибка: индекс выходит за пределы диапазона».

Версия кода 1:

struct Name: Identifiable, Hashable {
    var id: String = UUID().uuidString
    var name: String
    init(_ name: String) { self.name = name }
}

struct TestView: View {
    @State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3"), Name("test4"), Name("test5"), Name("test6"), Name("test7"), Name("test8")]

    var body: some View {
        List {
            ForEach(list) { name in
                HStack {
                    Text("\(0)")
                    Spacer()
                    Text("\(name.name)")
                }
                .background(Color.gray.opacity(0.001))
                .onTapGesture {
                    self.list = self.list.filter { $0 != name }
                }
            }
        }
    }
}

Версия кода 2:

struct TestView: View {
    @State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3"), Name("test4"), Name("test5"), Name("test6"), Name("test7"), Name("test8")]

    var body: some View {
        List {
            //ForEach(list.indices) { index in
            ForEach(list.indices, id: \.self) { index in
                HStack {
                    Text("\(index)")
                    Spacer()
                    Text("\(self.list[index].name)")
                }
                .background(Color.gray.opacity(0.001))
                .onTapGesture {
                    self.list.remove(at: index)
                }
            }
        }
    }
}

Ответы [ 3 ]

2 голосов
/ 20 февраля 2020

@ State - это свойство-обертка, которое заставит View, в котором он определен, пересчитать его тело.

В вашем случае, если вы удалите элемент по индексу,

HStack {
    Text("\(index)")
    Spacer()
    Text("\(self.list[index].name)")
}
.background(Color.gray.opacity(0.001))
.onTapGesture {
     self.list.remove(at: index)
 }

Текст внутри HStack

Text("\(self.list[index].name)")

cra sh, просто потому что список [index] больше не существует.

Использование

ForEach(list.indices, id:\.self) { index in ... }

вместо

ForEach(list.indices) { index in ... }

заставит SwiftUI воссоздать TestView (см. Id: \. Self в конструкторе ForEach)

SwiftUI сделает fre sh копию TestView при использовании значения fre sh имущества, завернутого в обертку свойства @State.

ОБНОВЛЕНИЕ

Пожалуйста, не обновляйте свой вопрос ...

Ваша последняя версия кода 4 - полная путаница, поэтому я переписал ее на то, что вы можете скопировать - вставить - запустить

import SwiftUI

struct Name: Identifiable, Hashable {
    var id: String = UUID().uuidString
    var name: String
    var marked: Bool
    init(_ name: String, marked: Bool = false) { self.name = name; self.marked = marked }
}

struct ContentView: View {
    @State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3", marked: true), Name("test4"), Name("test5", marked: true), Name("test6"), Name("test7"), Name("test8")]
    @State private var showMarkedOnly = false

    var body: some View {
        VStack{
            Toggle(isOn: $showMarkedOnly) {
                Text("show marked only")
            }.padding(.horizontal)
            List {
                ForEach(Array(zip(0..., list)).filter({!self.showMarkedOnly || $0.1.marked}), id: \.1.id) { index, name in
                    HStack {
                        Text("\(index)").foregroundColor(name.marked ? .red : .gray)
                        Spacer()
                        Text("\(name.name)")
                    }
                    .background(Color.gray.opacity(0.001))
                    .onTapGesture {
                        self.list.remove(at: index)
                    }
                }
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

она должна выглядеть как enter image description here

ОБНОВЛЕНИЕ, основанное на обсуждении

ForEach разные версии конструкторов используют внутренне различную функциональность ViewBuilder

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {

    /// Provides support for "if" statements in multi-statement closures, producing an `Optional` view
    /// that is visible only when the `if` condition evaluates `true`.
    public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View

    /// Provides support for "if" statements in multi-statement closures, producing
    /// ConditionalContent for the "then" branch.
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View

    /// Provides support for "if-else" statements in multi-statement closures, producing
    /// ConditionalContent for the "else" branch.
    public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}

Речь идет о «деталях реализации», и, надеюсь, это будет документировано в следующем выпуске. SwiftUI все еще находится на очень ранней стадии разработки, мы должны быть осторожны.

Давайте попробуем заставить SwiftUI идти своим путем! Первый отдельный RowView

struct RowView: View {
    var showMarkedOnly: Bool
    var index: Int
    var name: Name
    //@ViewBuilder
    var body: some View {
        if !self.showMarkedOnly || name.marked {
            HStack {
                Text(verbatim: index.description).foregroundColor(name.marked ? .red : .gray)
                Spacer()
                Text(verbatim: name.name)
            }
            .background(Color.gray.opacity(0.001))

        }
    }
}

Компилятор жалуется на

Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type

Раскомментируйте строку, чтобы обернуть наше тело

struct RowView: View {
    var showMarkedOnly: Bool
    var index: Int
    var name: Name
    @ViewBuilder
    var body: some View {
        if !self.showMarkedOnly || name.marked {
            HStack {
                Text(verbatim: index.description).foregroundColor(name.marked ? .red : .gray)
                Spacer()
                Text(verbatim: name.name)
            }
            .background(Color.gray.opacity(0.001))

        }
    }
}

Теперь мы можем использовать код так, как вам нравится : -)

struct ContentView: View {
    @State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3", marked: true), Name("test4"), Name("test5", marked: true), Name("test6"), Name("test7"), Name("test8")]
    @State private var showMarkedOnly = false

    var body: some View {
        VStack{
            Toggle(isOn: $showMarkedOnly) {
                Text("show marked only")
            }.padding(.horizontal)
            List {
                ForEach(Array(zip(0..., list)), id: \.1.id) { (index, name) in
                    RowView(showMarkedOnly: self.showMarkedOnly, index: index, name: name).onTapGesture {
                    self.list.remove(at: index)
                }
                }
            }
        }
    }
}

В конечном результате теперь используется конструкция buildIf<Content>, и весь код снова работает (результат выглядит точно так же, как показано выше)

1 голос
/ 20 февраля 2020

Это потому, что indices не ваше состояние, массив. Таким образом, вы обновляете массив, но @State на самом деле не достаточно умен, чтобы обновлять вычисляемые или нижестоящие свойства значения, которое он переносит. Если вы хотите индекс, сделайте его источником идентификаторов в вашем ForEach:

ForEach(0..<list.count, id: \.self) {
    // the rest should be ok here
}
0 голосов
/ 20 февраля 2020

Это потому, что SwiftUI перезагружает свой контент на основе предоставленных исходных данных. Вы можете решить ее, изменив свой

ForEach(list) { item in
    HStack {
        Text("\(item.id)")
        Spacer()
        Text("\(item.name)")
    }
    .onTapGesture {
        guard let itemIndex = self.list.firstIndex(of: item) else { return }
        self.list.remove(at:itemIndex)
    }
}

В противном случае , вы можете решить вашу проблему, используя встроенный метод .onDelete() (вы удаляете элементы, проводя по ним пальцем):

var body: some View {
    List {
        ForEach(list) { item in
            HStack {
                Text("\(item.id)")
                Spacer()
                Text("\(item.name)")
            }
            .background(Color.gray.opacity(0.001))
        }.onDelete(perform: delete)
    }
}

func delete(at offsets: IndexSet) {
    list.remove(atOffsets: offsets)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...