I'm writing an app with SwiftUI on macOS which uses Form view as an editor of user item. Each row in the form has a popover attached to a button inside the row that allows user to change icon of the row. The popover and changing of row icon are both working fine. But scroll position is always reset to top when the popover is dismissed.
Here's the codes:
import SwiftUI
struct ContentView: View {
@State private var name: String = ""
@State private var comment: String = ""
@State private var items: [Item] = []
enum ContentType: String, Identifiable {
case typeA
case typeB
case typeC
var id: String { rawValue }
}
var body: some View {
Form {
Section {
TextField("Name", text: $name, prompt: Text("Some name"))
TextField("Comment", text: $comment, prompt: Text("Some comment"))
}
Section {
ForEach(items) { item in
ItemView(item: item)
}
} header: {
HStack {
Text("\(items.count) Items")
Spacer()
Button {
addNewItem()
} label: {
Image(systemName: "plus")
}
}
}
}
.formStyle(.grouped)
}
private func addNewItem() {
let item = Item(
title: "Item \(Int.random(in: 1..<100))",
image: ImageItem.icon1)
items.append(item)
}
}
struct ItemView: View {
@ObservedObject var item: Item
@State private var popoverIsPresented = false
var body: some View {
HStack {
Button {
popoverIsPresented.toggle()
} label: {
Image(systemName: item.image.rawValue)
}
.buttonStyle(.plain)
.popover(isPresented: $popoverIsPresented) {
ImageItemPicker(selectedItem: $item.image, isPresented: $popoverIsPresented)
}
Text(item.title)
}
}
}
struct ImageItemPicker: View {
@Binding var selectedItem: ImageItem
@Binding var isPresented: Bool
var body: some View {
List(ImageItem.allCases, selection: $selectedItem) { item in
Button {
selectedItem = item
isPresented = false
} label: {
Label(item.rawValue, systemImage: item.rawValue)
}
}
}
}
enum ImageItem: String, CaseIterable, Identifiable {
case icon1
case icon2
case icon3
case icon4
case icon5
var id: String { rawValue }
var rawValue: String {
switch self {
case .icon1:
return "square.dashed"
case .icon2:
return "bookmark.square"
case .icon3:
return "star.square"
case .icon4:
return "heart.square"
case .icon5:
return "bell.square"
}
}
}
class Item: ObservableObject, Identifiable {
@Published var title: String
@Published var image: ImageItem
init(title: String, image: ImageItem) {
self.title = title
self.image = image
}
}
I have tried using a List instead of Form. The weird scroll behavior just disappeared. Since List on macOS doesn't have a style like "InsetGrouped", I have to use Form which has a "grouped" style. I'm wondering if this is a bug of SwiftUI or there exist anything wrong in the code.
The form is scrolling up probably because the text fields at the top are in focus.
In ItemView
, you can reset the focus whenever the popover button is pressed:
@Namespace var ns
@Environment(\.resetFocus) var reset
var body: some View {
HStack {
Button {
popoverIsPresented.toggle()
reset(in: ns)
} label: {
Image(systemName: item.image.rawValue)
}
...
This will cause the text fields to lose focus, and they will not be scrolled to, when the popover is dismissed.