SwiftUI Carousel的scrollPosition无法正确居中及初始化定位问题
自定义ScrollView Carousel的初始化定位问题
我用ScrollView实现了一个Carousel组件,希望通过父视图控制并初始化它的.scrollPosition(id:)属性,但遇到以下问题:
- 使用
HStack时,初始化定位完全失效 - 改用
LazyHStack后,初始化无法将选中项正确居中,选择第三个项目时偏移特别明显
数据模型代码
struct Item: Identifiable, Hashable { private(set) var id: UUID = .init() var color: Color var title: String var subTitle: String static var previews: [Item] = [ .init(color: .red, title: "World Clock", subTitle: "View the time in multiple cities around the world."), .init(color: .blue, title: "City Digital", subTitle: "Add a clock for a city to check the time at that location."), .init(color: .green, title: "City Analouge", subTitle: "Add a clock for a city to check the time at that location."), .init(color: .yellow, title: "Next Alarm", subTitle: "Display upcomiong alarm.") ] }
CustomPagingSlider组件代码
struct CustomPagingSlider<Content: View, TitleContent: View, Item: RandomAccessCollection>: View where Item: MutableCollection, Item.Element: Identifiable { /// View Properties @Binding var activeID: UUID? @State var data: Item /// Customization Properties var showsIndicator: ScrollIndicatorVisibility = .hidden var showPagingControl: Bool = true var disablePagingInteraction: Bool = false var titleScrollSpeed: CGFloat = 0.6 var pagingControlSpacing: CGFloat = 20 var spacing: CGFloat = 10 @ViewBuilder var content: (Binding<Item.Element>) -> Content @ViewBuilder var titleContent: (Binding<Item.Element>) -> TitleContent var body: some View { VStack(spacing: pagingControlSpacing) { ScrollView(.horizontal) { HStack(spacing: spacing) { // 使用HStack时完全失效 ForEach($data) { item in VStack(spacing: 0) { titleContent(item) .frame(maxWidth: .infinity) .visualEffect { content, geometryProxy in content .offset(x: scrollOffset(geometryProxy)) } content(item) } .containerRelativeFrame(.horizontal) } } .scrollTargetLayout() } .scrollIndicators(showsIndicator) .scrollTargetBehavior(.viewAligned) .scrollPosition(id: $activeID) } } func scrollOffset(_ proxy: GeometryProxy) -> CGFloat { let minX = proxy.bounds(of: .scrollView)?.minX ?? 0 return -minX * min(titleScrollSpeed, 1.0) } }
DetailedView代码
struct DetailedView: View { /// View Properties @State var items: [Item] @State var activeID: UUID? /// Customization Properties @State private var showPagingControl: Bool = false @State private var disablePagingInteraction: Bool = false @State private var pagingSpacing: CGFloat = 20 @State private var titleScrollSpeed: CGFloat = 0.75 @State private var stretchContent: Bool = true init(items: [Item], activeID: UUID?) { self._items = State(wrappedValue: items) self._activeID = State(wrappedValue: activeID) if let item = self.items.first(where: { $0.id == activeID }) { print("DetailedView init: \(item.title)") } } var body: some View { VStack { CustomPagingSlider(activeID: $activeID, data: items, showPagingControl: showPagingControl, disablePagingInteraction: disablePagingInteraction, titleScrollSpeed: titleScrollSpeed, pagingControlSpacing: pagingSpacing ) { $item in RoundedRectangle(cornerRadius: 15) .fill(item.color.gradient) .frame(width: stretchContent ? nil : 150, height: stretchContent ? 220 : 120) } titleContent: { $item in VStack(spacing: 5) { Text(item.title) .font(.largeTitle.bold()) .frame(height: 45) .background(.cyan) Text(item.subTitle) .foregroundStyle(.gray) .multilineTextAlignment(.center) .frame(height: 45) } } .safeAreaPadding([.horizontal], 35) List { Toggle("Show Paging Control", isOn: $showPagingControl) Toggle("Disable Page Interaction", isOn: $disablePagingInteraction) Toggle("Stretch Content", isOn: $stretchContent) Section("Title Scroll Speed") { Slider(value: $titleScrollSpeed) } Section("Paging Spacing") { Slider(value: $pagingSpacing, in: 20...40) } } .clipShape(.rect(cornerRadius: 15)) .padding(15) } .navigationTitle("Detailed View") .navigationBarTitleDisplayMode(.inline) } }
ContentView代码
struct ContentView: View { @State private var items: [Item] = Item.previews @State private var showDetailedView: Bool = false var body: some View { NavigationStack { List { ForEach(items) { item in NavigationLink(value: item) { VStack(alignment: .leading, spacing: 5) { Text(item.title) .font(.largeTitle.bold()) Text(item.subTitle) .foregroundStyle(.gray) .multilineTextAlignment(.leading) .frame(height: 45) } } } } .navigationTitle("Items") .navigationDestination(for: Item.self) { item in DetailedView(items: items, activeID: item.id) } } } }
问题表现
- 使用
HStack时,初始化定位完全无效,组件始终停留在第一个项目 - 改用
LazyHStack后,初始化时选中项无法居中,选择第三个项目时偏移现象明显
内容的提问来源于stack exchange,提问作者Robert Basamac
相关产品推荐
相关产品推荐

