当用户更改方向时,我如何从0开始重新计算yoffset
我正在尝试在底部使用自定义导航器创建滚动浏览量。当滚动浏览在其内部近距离时,导航项目应具有背景。
我已经使用了scrollViewReader将项目和yoffset保存在数组中。然后,我将yoffsetscrollvaluepreferencekeky放在滚动浏览中的整个hstack上。最后,我会听YoffSetsCrollValuePreferenceKey的值,并且是否确实将新值与数组中的项目的值进行了比较。如果该值存在,则将所选项目设置为属于该偏移的项目。
当我更改设备的方向时,我的问题就会发生。例如,如果用户滚动,请说例如在列表的中间说,将从该位置计算项目的位置。这意味着它不是第一个物品为0,而是具有负数(基于用户滚动多远的数字)。我需要根据scrollview中的位置来计算的项目,而不是基于用户在滚动浏览中的位置。有办法这样做吗?
我已经尝试在更改方向后将滚动浏览滚动回到第一项。该解决方案不起作用,因为在方向发生变化时,项目的位置发生了变化,这给出了其他一些错误的行为。我已经用完了所有的想法,所以希望有人可以在这里帮助我。 :)
我将提供简单的代码,在下面隔离问题的情况下!如果您还有更多问题或需要我提供更多信息,请告诉我。要陷入问题,请运行代码,请将列表滚动到中间(或除起始位置以外的其他地方)更改设备的方向,然后滚动到其他部分。现在,ScrollView下的导航视图与屏幕上的视图不同步。
import SwiftUI
struct ContentView: View {
@State private var numberPreferenceKeys = [NumberPreferenceKey]()
@State var selectedNumber = 0
@State var rectangleHeight: [CGFloat] = [
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000)
]
let colors: [Color] = [Color.blue, Color.red, Color.green, Color.gray, Color.purple]
var body: some View {
VStack {
ScrollViewReader { reader in
ScrollView(.horizontal) {
HStack {
ForEach(0..<5) { number in
Rectangle()
.fill(colors[number])
.frame(width: rectangleHeight[number], height: 200)
.id("\(number)")
.background(
GeometryReader { proxy in
if numberPreferenceKeys.count < 6{
var yOffSet = proxy.frame(in: .named("number")).minX
let _ = DispatchQueue.main.async {
var yPositiveOffset: CGFloat = 0
if number == 1, yOffSet < 0 {
yPositiveOffset = abs(yOffSet)
}
numberPreferenceKeys.append(
NumberPreferenceKey(
number: number,
yOffset: yOffSet + yPositiveOffset
)
)
}
}
Color.clear
}
)
}
}
.background(GeometryReader {
Color.clear.preference(
key: YOffsetScrollValuePreferenceKey.self,
value: -$0.frame(in: .named("number")).origin.x
)
})
.onPreferenceChange(YOffsetScrollValuePreferenceKey.self) { viewYOffsetKey in
DispatchQueue.main.async {
for numberPreferenceKey in numberPreferenceKeys where numberPreferenceKey.yOffset <= viewYOffsetKey {
selectedNumber = numberPreferenceKey.number
}
}
}
}
HStack {
ForEach(0..<5) { number in
ZStack {
if number == selectedNumber {
Rectangle()
.frame(width: 30, height: 30)
}
Rectangle()
.fill(colors[number])
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
reader.scrollTo("\(number)")
}
}
}
}
}
}
.coordinateSpace(name: "number")
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
numberPreferenceKeys = []
}
}
}
struct NumberPreferenceKey {
let number: Int
let yOffset: CGFloat
}
struct YOffsetScrollValuePreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
I'm trying to create a scrollview with a custom navigator in the bottom. The navigation item should get a background when the scrollview is inside it's proximity.
I've used a scrollviewReader to save the item and the yOffSet inside an array. Then I've given a YOffsetScrollValuePreferenceKey to the entire HStack inside the scrollview. Lastly I listen to if YOffsetScrollValuePreferenceKey value changes, and if it does compare the new value with the value of the items inside the array. If the value exists then I set the selected item to the item belonging to that offset.
My problem occurs when I change the orientation of the device. If a user has scrolled let's say for example to the middle of the list, the position of the items will be calculated from that position. This means that instead of the first item having a yOffSet of 0, it now has a negative number (based on how far the user has scrolled). I need the items yOffSet to be calculated based on their position inside the scrollview, not based on where the user is inside the scrollview. Is there a way to do this?
I've already tried to let the scrollview scroll back to the first item upon change of orientation. This solution did not work, as the position of the item changed when the orientation changed, and this gave some other buggy behaviour. I've ran all out of ideas so hoped someone could help me here. :)
I will provide simple code where the problem is isolated below! If you have any more questions or need me to provide more information please let me know. To run into the problem run the code, scroll the list to the middle (or anywhere else apart from the starting position) change the orientation of the device, and scroll to a different section. The navigation view under the scrollview now does not run in synch with which view is on screen.
import SwiftUI
struct ContentView: View {
@State private var numberPreferenceKeys = [NumberPreferenceKey]()
@State var selectedNumber = 0
@State var rectangleHeight: [CGFloat] = [
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000),
CGFloat.random(in: 500..<2000)
]
let colors: [Color] = [Color.blue, Color.red, Color.green, Color.gray, Color.purple]
var body: some View {
VStack {
ScrollViewReader { reader in
ScrollView(.horizontal) {
HStack {
ForEach(0..<5) { number in
Rectangle()
.fill(colors[number])
.frame(width: rectangleHeight[number], height: 200)
.id("\(number)")
.background(
GeometryReader { proxy in
if numberPreferenceKeys.count < 6{
var yOffSet = proxy.frame(in: .named("number")).minX
let _ = DispatchQueue.main.async {
var yPositiveOffset: CGFloat = 0
if number == 1, yOffSet < 0 {
yPositiveOffset = abs(yOffSet)
}
numberPreferenceKeys.append(
NumberPreferenceKey(
number: number,
yOffset: yOffSet + yPositiveOffset
)
)
}
}
Color.clear
}
)
}
}
.background(GeometryReader {
Color.clear.preference(
key: YOffsetScrollValuePreferenceKey.self,
value: -$0.frame(in: .named("number")).origin.x
)
})
.onPreferenceChange(YOffsetScrollValuePreferenceKey.self) { viewYOffsetKey in
DispatchQueue.main.async {
for numberPreferenceKey in numberPreferenceKeys where numberPreferenceKey.yOffset <= viewYOffsetKey {
selectedNumber = numberPreferenceKey.number
}
}
}
}
HStack {
ForEach(0..<5) { number in
ZStack {
if number == selectedNumber {
Rectangle()
.frame(width: 30, height: 30)
}
Rectangle()
.fill(colors[number])
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
reader.scrollTo("\(number)")
}
}
}
}
}
}
.coordinateSpace(name: "number")
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
numberPreferenceKeys = []
}
}
}
struct NumberPreferenceKey {
let number: Int
let yOffset: CGFloat
}
struct YOffsetScrollValuePreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
可以使用
AnchorPreference
以一种更轻松的方式来完成此操作,那么您只需要一个偏好,并且无需使用coordinatespace
。我已经更新了您的代码以使用此信息。它解决了您所遇到的问题,只需注意,它不会将当前滚动到旋转时滚动的项目重新中心,但是如果由于旋转而更改,它确实会更改选择。
This can be done in a much easier way using
anchorPreference
, then you only need one preference and there is no need to use acoordinateSpace
.I've updated your code to use this instead. It solves the issue you are having, just note that it will not re-center the currently scrolled to item on rotation, but it does change the selection if it changed because of rotation.