Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
453 views
in Technique[技术] by (71.8m points)

ios - List Item with Toggle subview and selection gesture

I have a List and items containing a Toggle view. I would like to handle selection and toggle state separately. the toggle should be disabled when list item is not selected. The problem is:

  1. first tap - selects the list item. (selected = true)
  2. tap on toggle - set selection = false

On UIKit, Button/Switch/etc... catch the 'tap' and do not pass through to TableView selection state.

struct ListView: View {
    @State var selected = Set<Int>()
    let items = (1...10).map { ItemDataModel(id: $0) }
    
    var body: some View {
        List(items) { item in
            ListItemView(dataModel: item)
                .onTapGesture { if !(selected.remove(item.id) != .none) { selected.insert(item.id) }}
        }
    }
}

struct ListItemView: View {
    @ObservedObject var dataModel: ItemDataModel
    
    var body: some View {
        HStack {
            Text(dataModel.title)
            Spacer()
            Toggle(
                isOn:Binding<Bool>(get: {dataModel.isOn}, set: {dataModel.isOn = $0}),
                label: { Text("toggle") })
        }
    }
}

class ItemDataModel: Hashable, Identifiable, ObservableObject {
    let id: Int
    let title: String
    @Published var isOn: Bool = false
    
    init(id: Int) {
        self.id = id
        title = "item (id)"
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
    }
    
    static func == (lhs: ItemDataModel, rhs: ItemDataModel) -> Bool {
        return lhs.id == rhs.id
    }
}

[Please try to ignore syntax errors (if exsist) and focus on the gesture issue]


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

First, your [ItemDataModel] array should be moved outside the body, so it's not recreated every time:

struct ListView: View {
    @State var selected = Set<Int>()
    let items = (1...10).map(ItemDataModel.init) // move outside the `body`

    var body: some View {
        VStack {
            Text(String(describing: selected))
            List(items) { item in
                ListItemView(dataModel: item)
                    .onTapGesture { if !(selected.remove(item.id) != .none) { selected.insert(item.id) }}
            }
        }
    }
}

Then, make sure that the Toggle in your ListItemView doesn't take all the space (that's the default behaviour) and attach onTapGesture to override the parent's gesture:

struct ListItemView: View {
    @ObservedObject var dataModel: ItemDataModel

    var body: some View {
        HStack {
            Text(dataModel.title)
            // Text("toggle") // if necessary add Toggle's label as `Text`
            Spacer()
            Toggle("", isOn: $dataModel.isOn) // use another initialiser
                .fixedSize() // limit Toggle's width
                .background(Color.red) // debug-only, to see the real frame
                .onTapGesture {} // override tap gestures
        }
        .contentShape(Rectangle()) // make empty space *clickable*
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...