SwiftUI - 防止列表在项目选择时将滚动位置重置为顶部
Posted
技术标签:
【中文标题】SwiftUI - 防止列表在项目选择时将滚动位置重置为顶部【英文标题】:SwiftUI - Prevent List from resetting scroll position to top on item selection 【发布时间】:2019-06-26 09:30:23 【问题描述】:所以,一直在搞乱 SwiftUI,除了在选择项目时列表会将其滚动位置重置到顶部之外,它都可以正常工作。我做任何事情都无法解决这个问题。代码 -
struct FontSettingsView : View
@ObjectBinding var settings: FontSettings
var body: some View
NavigationView
ContentView(settings: settings)
.navigationBarTitle(Text("Font"), displayMode: .inline)
struct ContentView: View
@State var settings: FontSettings
var body: some View
VStack
HeaderView(settings: $settings)
FontListView(excludedFontNames: settings.excludedFontNames) fontName in
self.$settings.name.value = fontName
struct HeaderView: View
@Binding var settings: FontSettings
var body: some View
VStack
HStack
VStack (alignment: .leading)
Text("Custom font size:")
.font(.body)
Text("If disabled, will use system settings")
.font(.caption)
Toggle(isOn: $settings.isCustomSize)
Text("")
Slider(value: $settings.size, from: 10, through: 30, by: 0.1)
.disabled($settings.isCustomSize.value == false)
Text("Current font: \($settings.name.value)")
.customFont(using: self.settings)
.padding()
.animation(.default)
.padding()
struct FontListView: View
struct FontName: Identifiable
var id = UUID()
let value: String
private let fontNames: [FontName]
private let tapped: ((String) -> ())?
init(excludedFontNames: [String] = [], tapped: ((String) -> ())?)
self.fontNames = UIFont.familyNames.sorted().filter( excludedFontNames.contains($0) == false ).map FontName(value: $0)
self.tapped = tapped
var body: some View
List(fontNames) fontName in
Button(action:
self.tapped?(fontName.value)
)
Text(fontName.value).customFont(named: fontName.value)
不幸的是,所有文档和示例似乎都与选择项目时导航到新屏幕有关,这不是我的意图。
为想要运行整个程序的任何人提供一些额外的代码 -
class FontSettings: BindableObject
private static let defaultSize: CGFloat = 20
var didChange = PassthroughSubject<Void, Never>()
var excludedFontNames: [String] = []
var name: String = "Helvetica"
didSet
self.didChange.send()
var size: CGFloat = FontSettings.defaultSize
didSet
self.didChange.send()
var isCustomSize: Bool = false
didSet
if self.isCustomSize == false
self.size = FontSettings.defaultSize
self.didChange.send()
extension View
func customFont(named fontName: String, style: UIFont.TextStyle = .body) -> Self.Modified<CustomFont>
return self.modifier(CustomFont(fontName: fontName, textStyle: style))
func customFont(named fontName: String, size: CGFloat) -> Self.Modified<CustomFont>
return self.modifier(CustomFont(fontName: fontName, size: size))
func customFont(using settings: FontSettings) -> Self.Modified<CustomFont>
if settings.isCustomSize
return self.modifier(CustomFont(fontName: settings.name, size: settings.size))
return self.modifier(CustomFont(fontName: settings.name, textStyle: .body))
struct CustomFont: ViewModifier
let fontName: String
let fontSize: CGFloat?
let textStyle: UIFont.TextStyle?
init(fontName: String, textStyle: UIFont.TextStyle)
self.fontName = fontName
self.textStyle = textStyle
self.fontSize = nil
init(fontName: String, size: CGFloat)
self.fontName = fontName
self.fontSize = size
self.textStyle = nil
// trigger view refresh when the ContentSizeCategory changes
@Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
func body(content: Content) -> some View
if let fontSize = self.fontSize
return content.font(.custom(self.fontName, size: fontSize))
guard let textStyle = self.textStyle, let size = self.fontSizes[textStyle] else
fatalError("Unrecognized textStyle")
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
let fontSize = fontMetrics.scaledValue(for: size)
return content.font(.custom(self.fontName, size: fontSize))
// normal font sizes per style, as defined by Apple
private let fontSizes: [UIFont.TextStyle: CGFloat] = [
.largeTitle: 34,
.title1: 28,
.title2: 22,
.title3: 20,
.headline: 17,
.body: 17,
.callout: 16,
.subheadline: 15,
.footnote: 13,
.caption1: 12,
.caption2: 11
]
【问题讨论】:
你有没有找到解决办法? @Jordy 不,还没有 【参考方案1】:我确信有更好的方法,但这是我用于基本选择的方法
基本上存储一组id,并将每个单元格绑定到该组
然后在单元格选择时从集合中添加/删除。
struct Person: Identifiable
var name: String
var id = UUID().uuidString
struct ContentView: View
@State private var selectedItems = Set<String>([])
@State private var items = [
Person(name: "A"),
Person(name: "B"),
Person(name: "C"),
Person(name: "D"),
Person(name: "E"),
Person(name: "F"),
Person(name: "G"),
Person(name: "H"),
Person(name: "I"),
Person(name: "J"),
Person(name: "K"),
Person(name: "L")
]
var body: some View
List
ForEach(self.items) item in
Row(item: item, selectedItems: self.$selectedItems)
.listRowInsets(EdgeInsets.appDefault())
struct Row: View
var item: Person
@State private var isSelected: Bool = false
// ref to list of current selected items
@Binding var selectedItems: Set<String>
private func isInSelectedList(_ item: Person) -> Bool
selectedItems.contains(item.id)
var body: some View
HStack
VStack(alignment: .leading)
Text(item.name)
.font(.headline)
Spacer()
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.font(Font.system(size: 40, weight: .light))
.foregroundColor(isSelected ? Color(UIColor.systemGreen) : Color.secondary.opacity(0.6))
.onTapGesture
// use the main view as the view model
self.addOrRemoveFromSelectedList(self.item)
// tap circle to add - then re check if should show tick or not
self.isSelected = self.isInSelectedList(self.item)
.frame(height: 80)
.padding([.top, .bottom])
.onAppear
// after scrolling away the selection is set when cell shown again
self.isSelected = self.isInSelectedList(self.item)
private func addOrRemoveFromSelectedList(_ item: Person)
if selectedItems.contains(item.id)
selectedItems.remove(item.id)
else
selectedItems.insert(item.id)
// stops the annoying inseting of cell when selected
extension EdgeInsets
static func appDefault() -> EdgeInsets
return .init(top: 0, leading: 16, bottom: 0, trailing: 16)
【讨论】:
谢谢你,我会试一试的——我非常喜欢 SwiftUI 的某些部分,但是我们有时不得不跳过一些障碍来做最简单的事情,这让人感觉像是一场持久战 【参考方案2】:嘿,我发现如果你在 ScrollView 中使用 ForEach,当你使用 onTap 手势识别器时它不会重置滚动位置
ScrollView
ForEach(podcast, id: \.id) item in
PodcastRow(item: item, audioPlayer: audioPlayer)
.padding(.leading)
.navigationBarTitle(Text("\(podcastTitle)"))
.onAppear
self.loadPodcasts()
希望这就是你要找的东西
【讨论】:
以上是关于SwiftUI - 防止列表在项目选择时将滚动位置重置为顶部的主要内容,如果未能解决你的问题,请参考以下文章
如何使用滚动视图单击列表部分内的项目以导航到详细信息页面 SwiftUI