ScrollView在设备上为什么会将内容高度更改为随机值,但在预览上不会?

huangapple go评论113阅读模式
英文:

Why is ScrollView changing content height to a random value on device but not on preview?

问题

我目前使用的是 Xcode 版本 14.3.1(14E300c)。

在代码中,滚动视图具有一个对象数组,我正在使用 ForEach() 进行迭代。当用户滚动时,比如滚动到第30个对象(每个对象都有编号,仅用于演示目的),锚点值为0.00135(您可以将其视为与该对象的偏移值),我希望用户能够在取消视图初始化之前返回到确切的位置;就像几乎每个社交媒体应用程序一样。

我尝试使用 ScrollViewReader.scrollTo(<id: , anchor:) 来实现这一点,但没有成功。在预览中它运行得很完美,但一旦我使用模拟器或设备(iPhone 14),它就会在随机序列中调整我的滚动视图内容高度。

请问有人可以解释为什么这在预览中可以完美运行,但在设备上却不起作用;以及如何使其在设备上运行而没有这个奇怪的错误?

因此,问题仅在接近滚动内容底部时发生;而奇怪的是,如果您在滚动内容顶部附近滚动,它会自行修复。

// 可能引起问题的视图
struct ScrollViewWithSavedPosition: View {
    @ObservedObject var scrollViewSavedValue: ScrollViewSavedValue
    @State private var isViewLoaded: Bool = false
    let geoProxy: GeometryProxy
    let maxScrollableHeight: CGFloat = 4809
    
    var body: some View {
        ScrollViewReader { scrollProxy in
            ScrollView(showsIndicators: false) {
                LazyVStack {
                    ForEach(0..<34) { poster in
                        ZStack {
                            Rectangle()
                                .fill(.black)
                                .frame(width: geoProxy.size.width * 0.4,
                                       height: geoProxy.size.height * 0.2)
                            
                            Text("\(poster)")
                                .foregroundColor(.orange)
                        }
                    }
                }
                .id("scrollPosition")
                .background(
                    GeometryReader {
                        Color.orange
                            .preference(key: CGPointPK2.self,
                                        value: $0.frame(in: .global).origin)
                    }
                )
                .onPreferenceChange(CGPointPK2.self) { scrollPosition in
                    DispatchQueue.main.async {
                        isViewLoaded = true
                    }
                    
                    if isViewLoaded {
                        let offsetValue = (-1 * (scrollPosition.y - geoProxy.safeAreaInsets.top)) / maxScrollableHeight
                        scrollViewSavedValue.scrollOffsetValue = offsetValue
                    }
                    
                    print(scrollViewSavedValue.scrollOffsetValue)
                }
            }
            .onAppear {
                scrollProxy.scrollTo("scrollPosition", anchor: UnitPoint(x: 0, y: scrollViewSavedValue.scrollOffsetValue))
            }
        }
        .preferredColorScheme(.light)
    }
}

struct CGPointPK2: PreferenceKey {
    static var defaultValue: CGPoint = .zero
    static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
}

class ScrollViewSavedValue: ObservableObject {
    @Published var scrollOffsetValue: CGFloat = 0
    @Published var selectedTab: Int = 1
}

struct TabBarView: View {
    @ObservedObject var scrollViewSavedValue: ScrollViewSavedValue
    let geoProxy: GeometryProxy
    
    var body: some View {
        VStack {
            Spacer()
            
            ZStack {
                Rectangle()
                    .fill(.blue)
                    .frame(width: geoProxy.size.width, height: 80)
                
                HStack(spacing: 100) {
                    Button {
                        DispatchQueue.main.async {
                            scrollViewSavedValue.selectedTab = 1
                        }
                    } label: {
                        ZStack {
                            Circle()
                                .fill(.black)
                                .frame(width: 60, height: 60)
                            
                            Text("View 1")
                                .foregroundColor(.white)
                                .font(.system(size: 12))
                                .fontWeight(.bold)
                        }
                    }
                    
                    Button {
                        DispatchQueue.main.async {
                            scrollViewSavedValue.selectedTab = 2
                        }
                    } label: {
                        ZStack {
                            Circle()
                                .fill(.black)
                                .frame(width: 60, height: 60)
                            
                            Text("View 2")
                                .foregroundColor(.white)
                                .font(.system(size: 12))
                                .fontWeight(.bold)
                        }
                    }
                }
            }
        }
        .ignoresSafeArea()
    }
}

struct PresentationView: View {
    @StateObject private var scrollViewSavedValue = ScrollViewSavedValue()
    
    var body: some View {
        GeometryReader { geoProxy in
            switch scrollViewSavedValue.selectedTab {
            case 1:
                ScrollViewWithSavedPosition(scrollViewSavedValue: scrollViewSavedValue, geoProxy: geoProxy)
            default:
                Text("View 2")
                    .foregroundColor(.black)
            }
            
            TabBarView(scrollViewSavedValue: scrollViewSavedValue, geoProxy: geoProxy)
        }
    }
}
英文:

I am currently using **Xcode Version 14.3.1** (14E300c).

The scrollview in the code has an array of objects that I am iterating through with, ForEach(). When the user scrolls, lets say to object 30(each object is numbered, for demonstration purposes), with an anchor value of 0.00135(you can think of this as the offset value in relation to said object), i want the user to be able to return to the exact location prior to deinitializing the view; exactly like, pretty much, every social media application.

I have attempted to do this by using ScrollViewReader.scrollTo(&lt;id: , anchor:), but to no avail. It works perfectly with preview, but as soon as I use the simulator or a device(iPhone 14), it starts to resize my ScrollView Content height in random sequences.

Can someone please explain why this would be working perfectly in preview but not on devices; and how can I make it work for devices without this strange bug?

So the issue happens only when you reach near the end(bottom) of the scrollable content; and oddly enough, if you scroll near the beginning(top) of the scrollable content, it fixes itself.

**// VIEW THAT IS PROBABLY CAUSING THE ISSUE**
struct ScrollViewWithSavedPosition: View {
@ObservedObject var scrollViewSavedValue: ScrollViewSavedValue
@State private var isViewLoaded: Bool = false
let geoProxy: GeometryProxy
let maxScrollableHeight: CGFloat = 4809
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView(showsIndicators: false) {
LazyVStack {
ForEach(0..&lt;34) { poster in
ZStack {
Rectangle()
.fill(.black)
.frame(width: geoProxy.size.width * 0.4,
height: geoProxy.size.height * 0.2)
Text(&quot;\(poster)&quot;)
.foregroundColor(.orange)
}
}
}
.id(&quot;scrollPosition&quot;)
.background(
GeometryReader {
Color.orange
.preference(key: CGPointPK2.self,
value: $0.frame(in: .global).origin)
}
)
.onPreferenceChange(CGPointPK2.self) { scrollPosition in
DispatchQueue.main.async {
isViewLoaded = true
}
if isViewLoaded {
let offsetValue = (-1 * (scrollPosition.y - geoProxy.safeAreaInsets.top)) / maxScrollableHeight
scrollViewSavedValue.scrollOffsetValue = offsetValue
}
print(scrollViewSavedValue.scrollOffsetValue)
}
}
.onAppear {
scrollProxy.scrollTo(&quot;scrollPosition&quot;, anchor: UnitPoint(x: 0, y: scrollViewSavedValue.scrollOffsetValue))
}
}
.preferredColorScheme(.light)
}
}
struct CGPointPK2: PreferenceKey {
static var defaultValue: CGPoint = .zero
static func reduce(value: inout CGPoint, nextValue: () -&gt; CGPoint) { }
}
class ScrollViewSavedValue: ObservableObject {
@Published var scrollOffsetValue: CGFloat = 0
@Published var selectedTab: Int = 1
}
struct TabBarView: View {
@ObservedObject var scrollViewSavedValue: ScrollViewSavedValue
let geoProxy: GeometryProxy
var body: some View {
VStack {
Spacer()
ZStack {
Rectangle()
.fill(.blue)
.frame(width: geoProxy.size.width, height: 80)
HStack(spacing: 100) {
Button {
DispatchQueue.main.async {
scrollViewSavedValue.selectedTab = 1
}
} label: {
ZStack {
Circle()
.fill(.black)
.frame(width: 60, height: 60)
Text(&quot;View 1&quot;)
.foregroundColor(.white)
.font(.system(size: 12))
.fontWeight(.bold)
}
}
Button {
DispatchQueue.main.async {
scrollViewSavedValue.selectedTab = 2
}
} label: {
ZStack {
Circle()
.fill(.black)
.frame(width: 60, height: 60)
Text(&quot;View 2&quot;)
.foregroundColor(.white)
.font(.system(size: 12))
.fontWeight(.bold)
}
}
}
}
}
.ignoresSafeArea()
}
}
struct PresentationView: View {
@StateObject private var scrollViewSavedValue = ScrollViewSavedValue()
var body: some View {
GeometryReader { geoProxy in
switch scrollViewSavedValue.selectedTab {
case 1:
ScrollViewWithSavedPosition(scrollViewSavedValue: scrollViewSavedValue, geoProxy: geoProxy)
default:
Text(&quot;View 2&quot;)
.foregroundColor(.black)
}
TabBarView(scrollViewSavedValue: scrollViewSavedValue, geoProxy: geoProxy)
}
}
}

答案1

得分: 0

Hello and welcome to Stack Overflow,

在玩弄您的代码一段时间后,我认为问题出在滚动高度不是恒定的,这使得确定应该滚动到哪里变得困难。最初,我认为通过使用数学,我可以找到答案,但经过一些测试,我发现滚动高度不仅受顶部填充的影响,还受底部填充和标签栏的大小影响。因此,每部手机都需要单独测试。

一个更简单的解决方案是在切换到视图2时,不要移除滚动视图,我已将其重命名为 ScrollPosterView。可以通过使用 opacitydisabled 修改器来实现这一点。这确保滚动视图仍然被呈现,但用户无法看到或操作它。使用这种方法,不需要保存滚动视图的滚动位置。由于这看起来像是社交媒体类型的应用程序,不需要重新加载图像,可以节省处理能力。

这样做如下所示(我已简化了代码):

struct ContentView: View {
    
    @State var selectedTab: Int = 1
    
    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                ScrollPosterView()
                    .opacity(selectedTab == 1 ? 1 : 0)
                    .disabled(selectedTab == 2)
                ScrollView {
                    VStack {
                        Text("View 2")
                            .foregroundColor(.black)
                        Spacer()
                    }
                }
                .disabled(selectedTab == 1)
                .opacity(selectedTab == 2 ? 1 : 0)

            }
        
            TabBarView(selectedTab: $selectedTab)
        }
    }
}

struct ScrollPosterView: View {
    var body: some View {
        ScrollView(showsIndicators: false) {
            LazyVStack(spacing: 10) {
                ForEach(0..<20) { poster in
                    ZStack {
                        Rectangle()
                            .fill(.black)
                            .frame(width: 100,
                                   height: 100)
                        
                        Text("\(poster)")
                            .foregroundColor(.orange)
                    }
                }
            }
            .background(Color.orange)
        }
        .preferredColorScheme(.light)
    }
}

struct TabBarView: View {
    
    @Binding var selectedTab: Int
    
    var body: some View {
        ZStack {
            HStack(spacing: 100) {
                Button {
                    selectedTab = 1
                } label: {
                    ZStack {
                        Circle()
                            .fill(.black)
                            .frame(width: 60, height: 60)
                        
                        Text("View 1")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                            .fontWeight(.bold)
                    }
                }
                
                Button {
                    selectedTab = 2
                } label: {
                    ZStack {
                        Circle()
                            .fill(.black)
                            .frame(width: 60, height: 60)
                        
                        Text("View 2")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                            .fontWeight(.bold)
                    }
                }
            }
        }
        .frame(height: 70)
        .frame(maxWidth: .infinity)
        .background(Rectangle()
            .foregroundColor(.blue)
            .ignoresSafeArea())
    }
}

希望这有助于解决您的问题。如果您有任何其他翻译需求,请随时告诉我。

英文:

Hello and welcome to Stack Overflow,

After playing around with your code for a while, I believe the problem lies with the fact that the scroll height is not constant which it makes it difficult to determine where you should scroll to. I initially thought through using mathematics I could figure it out, but after some testing I found the scroll height is affected not only by the top padding, but also the bottom padding and the size of the tab bar. So each phone will need to be tested individually.

A much simpler solution is when changing to view 2, not to remove the scroll view, which I have renamed to ScrollPosterView. This can be done by using the opacity and disable modifiers. This ensures that the scroll view is still rendered but is not able to be seen or operated on by the user. Using this method, the scroll view's scroll position does not need to be saved. As this looks like to be a social media type app, the images do not need to be reloaded, saving on processing power.

This is done as so (where I have simplified the code):

struct ContentView: View {
@State var selectedTab: Int = 1
var body: some View {
VStack(spacing: 0) {
ZStack {
ScrollPosterView()
.opacity(selectedTab == 1 ? 1 : 0)
.disabled(selectedTab == 2)
ScrollView {
VStack {
Text(&quot;View 2&quot;)
.foregroundColor(.black)
Spacer()
}
}
.disabled(selectedTab == 1)
.opacity(selectedTab == 2 ? 1 : 0)
}
TabBarView(selectedTab: $selectedTab)
}
}
}

Where ScrollPosterView is:

struct ScrollPosterView: View {
var body: some View {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 10) {
ForEach(0..&lt;20) { poster in
ZStack {
Rectangle()
.fill(.black)
.frame(width: 100,
height: 100)
Text(&quot;\(poster)&quot;)
.foregroundColor(.orange)
}
}
}
.background(Color.orange)
}
.preferredColorScheme(.light)
}
}

And TabBarView is:

struct TabBarView: View {
@Binding var selectedTab: Int
var body: some View {
ZStack {
HStack(spacing: 100) {
Button {
selectedTab = 1
} label: {
ZStack {
Circle()
.fill(.black)
.frame(width: 60, height: 60)
Text(&quot;View 1&quot;)
.foregroundColor(.white)
.font(.system(size: 12))
.fontWeight(.bold)
}
}
Button {
selectedTab = 2
} label: {
ZStack {
Circle()
.fill(.black)
.frame(width: 60, height: 60)
Text(&quot;View 2&quot;)
.foregroundColor(.white)
.font(.system(size: 12))
.fontWeight(.bold)
}
}
}
}
.frame(height: 70)
.frame(maxWidth: .infinity)
.background(Rectangle()
.foregroundColor(.blue)
.ignoresSafeArea())
}
}

huangapple
  • 本文由 发表于 2023年6月8日 10:25:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76428200.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定