SwiftUI-防止列表在项目选择时将滚动位置重置为顶部
所以,我们一直在摆弄SwiftUI&它都在工作,只是每当选择一个项目时,列表就会将其滚动位置重置为顶部。我做什么都不能解决这个问题。代码-SwiftUI-防止列表在项目选择时将滚动位置重置为顶部,swiftui,Swiftui,所以,我们一直在摆弄SwiftUI&它都在工作,只是每当选择一个项目时,列表就会将其滚动位置重置为顶部。我做什么都不能解决这个问题。代码- struct FontSettingsView : View { @ObjectBinding var settings: FontSettings var body: some View { NavigationView { ContentView(settings: settings)
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
]
}
类设置:BindableObject{
私有静态let defaultSize:CGFloat=20
var didChange=PassthroughSubject()
var excludedFontNames:[字符串]=[]
变量名称:String=“Helvetica”{
迪塞特{
self.didChange.send()
}
}
变量大小:CGFloat=FontSettings.defaultSize{
迪塞特{
self.didChange.send()
}
}
var isCustomSize:Bool=false{
迪塞特{
如果self.isCustomSize==false{
self.size=FontSettings.defaultSize
}
self.didChange.send()
}
}
}
扩展视图{
func customFont(名为fontName:String,样式:UIFont.TextStyle=.body)->Self.Modified{
返回self.modifier(CustomFont(fontName:fontName,textStyle:style))
}
func customFont(名为fontName:String,size:CGFloat)->Self.Modified{
返回self.modifier(CustomFont(fontName:fontName,size:size))
}
func customFont(使用设置:FontSettings)->Self.Modified{
if settings.isCustomSize{
返回self.modifier(CustomFont(fontName:settings.name,size:settings.size))
}
返回self.modifier(CustomFont(fontName:settings.name,textStyle:.body))
}
}
结构自定义字体:视图修改器{
让fontName:字符串
让fontSize:CGFloat?
让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=大小
self.textStyle=nil
}
//ContentSizeCategory更改时触发视图刷新
@环境(\.sizeCategory)变量sizeCategory:ContentSizeCategory
func正文(内容:内容)->某些视图{
如果让fontSize=self.fontSize{
返回content.font(.custom(self.fontName,size:fontSize))
}
guard let textStyle=self.textStyle,let size=self.fontSizes[textStyle]else{
fatalError(“无法识别的文本样式”)
}
让fontMetrics=UIFontMetrics(forTextStyle:textStyle)
让fontSize=fontMetrics.scaledValue(for:size)
返回content.font(.custom(self.fontName,size:fontSize))
}
//每个样式的正常字体大小,如Apple所定义
私有let字体大小:[UIFont.TextStyle:CGFloat]=[
.largeTitle:34,
.标题1:28,
.标题2:22,
.标题3:20,
.标题:17,
.机构:17,
.编号:16,
.分目:15,
.脚注:13,
.说明1:12,
.字幕2:11
]
}
我确信有更好的方法,但这就是我用于基本选择的方法
基本上存储一组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)
}
}
struct Person:可识别{
变量名称:String
var id=UUID().uuistring
}
结构ContentView:View{
@状态私有变量selectedItems=Set([])
@国家私有var项目=[
个人(姓名:“A”),
个人(姓名:“B”),
个人(姓名:“C”),
个人(姓名:“D”),
个人(姓名:“E”),
个人(姓名:“F”),
个人(姓名:“G”),
个人(姓名:“H”),
个人(姓名:“本人”),
个人(姓名:“J”),
个人(姓名:“K”),
人(姓名:“L”)
]
var body:一些观点{
名单{
ForEach(self.items){item in
行(项目:项目,selectedItems:self.$selectedItems)
}.listRowInsets(EdgeInsets.appDefault())
}
}
}
结构行:视图{
变量项:人
@选择的国家私有变量:Bool=false
//参考当前选定项目的列表
@绑定变量selectedItems:Set
私有函数IsSelectedList(uItem:Person)->Bool{
selectedItems.contains(item.id)
}
var body:一些观点{
HStack{
VStack(对齐:。前导){
文本(项目名称)
.font(.headline)
}
垫片()
图像(系统名称:isSelected?“选中标记.圆圈.填充”:“圆圈”)
.font(字体系统(大小:40,重量:轻))
.foregroundColor(isSelected?颜色(UIColor.systemGreen):颜色。次要。不透明度(0.6))
.ontapsigne{
//使用主视图作为视图模型
self.add或removefromselectedlist(self.item)
//点击圆圈添加-然后重新检查是否应显示勾号
self.isSelected=self.isselectedlist(self.item)
}
}
.框架(高度:80)
.padding([.top、.bottom])
奥纳佩尔先生{
//滚动后,当单元格再次显示时,将设置所选内容
self.isSelected=self.isselectedlist(self.item)
}
}
private func add或remove from Selected List(\项:个人){
如果选择EdItems.contains(item.id){
选择editems.remove(item.id)
}否则{
选择editems.insert(item.id)
}
}
}
//选择此选项后,停止讨厌的单元格插入
扩展边集{
静态函数appDefault()->EdgeInSet{
return.init(顶部:0,前导:16,底部:0,尾随:16)
}
}
嘿,我发现如果在ScrollView中使用ForEach,它将不会重置scroll po
ScrollView {
ForEach(podcast, id: \.id) { item in
PodcastRow(item: item, audioPlayer: audioPlayer)
.padding(.leading)
}
.navigationBarTitle(Text("\(podcastTitle)"))
.onAppear {
self.loadPodcasts()
}
}