SwiftUI 创建以点为指标的图像滑块
Posted
技术标签:
【中文标题】SwiftUI 创建以点为指标的图像滑块【英文标题】:SwiftUI create image slider with dots as indicators 【发布时间】:2019-11-17 01:13:52 【问题描述】:我想为图像创建一个滚动视图/滑块。请参阅我的示例代码:
ScrollView(.horizontal, showsIndicators: true)
HStack
Image(shelter.background)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 300)
Image("pacific")
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 300)
虽然这可以让用户滑动,但我希望它有点不同(类似于 UIKit 中的 PageViewController)。我希望它的行为类似于我们从许多应用程序中知道的以点为指标的典型图像滑块:
-
它应始终显示完整图像,中间没有 - 因此如果用户在中间拖动并停止,它将自动跳转到完整图像。
我想要点作为指标。
因为我看到很多应用程序都使用这样的滑块,所以一定有已知的方法,对吧?
【问题讨论】:
在Interfacing with UIKit Apple 教程中,准确地演示了如何按照您的要求进行操作。 【参考方案1】:今年的 SwiftUI 中没有内置的方法。我相信将来会出现系统标准的实现。
在短期内,您有两种选择。正如 Asperi 所指出的,Apple 自己的教程中有一节介绍了从 UIKit 包装 PageViewController 以在 SwiftUI 中使用(请参阅Interfacing with UIKit)。
第二种选择是自己动手。在 SwiftUI 中制作类似的东西是完全可能的。这是一个概念证明,可以通过滑动或绑定来更改索引:
struct PagingView<Content>: View where Content: View
@Binding var index: Int
let maxIndex: Int
let content: () -> Content
@State private var offset = CGFloat.zero
@State private var dragging = false
init(index: Binding<Int>, maxIndex: Int, @ViewBuilder content: @escaping () -> Content)
self._index = index
self.maxIndex = maxIndex
self.content = content
var body: some View
ZStack(alignment: .bottomTrailing)
GeometryReader geometry in
ScrollView(.horizontal, showsIndicators: false)
HStack(spacing: 0)
self.content()
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
.content.offset(x: self.offset(in: geometry), y: 0)
.frame(width: geometry.size.width, alignment: .leading)
.gesture(
DragGesture().onChanged value in
self.dragging = true
self.offset = -CGFloat(self.index) * geometry.size.width + value.translation.width
.onEnded value in
let predictedEndOffset = -CGFloat(self.index) * geometry.size.width + value.predictedEndTranslation.width
let predictedIndex = Int(round(predictedEndOffset / -geometry.size.width))
self.index = self.clampedIndex(from: predictedIndex)
withAnimation(.easeOut)
self.dragging = false
)
.clipped()
PageControl(index: $index, maxIndex: maxIndex)
func offset(in geometry: GeometryProxy) -> CGFloat
if self.dragging
return max(min(self.offset, 0), -CGFloat(self.maxIndex) * geometry.size.width)
else
return -CGFloat(self.index) * geometry.size.width
func clampedIndex(from predictedIndex: Int) -> Int
let newIndex = min(max(predictedIndex, self.index - 1), self.index + 1)
guard newIndex >= 0 else return 0
guard newIndex <= maxIndex else return maxIndex
return newIndex
struct PageControl: View
@Binding var index: Int
let maxIndex: Int
var body: some View
HStack(spacing: 8)
ForEach(0...maxIndex, id: \.self) index in
Circle()
.fill(index == self.index ? Color.white : Color.gray)
.frame(width: 8, height: 8)
.padding(15)
还有一个演示
struct ContentView: View
@State var index = 0
var images = ["10-12", "10-13", "10-14", "10-15"]
var body: some View
VStack(spacing: 20)
PagingView(index: $index.animation(), maxIndex: images.count - 1)
ForEach(self.images, id: \.self) imageName in
Image(imageName)
.resizable()
.scaledToFill()
.aspectRatio(4/3, contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 15))
PagingView(index: $index.animation(), maxIndex: images.count - 1)
ForEach(self.images, id: \.self) imageName in
Image(imageName)
.resizable()
.scaledToFill()
.aspectRatio(3/4, contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 15))
Stepper("Index: \(index)", value: $index.animation(.easeInOut), in: 0...images.count-1)
.font(Font.body.monospacedDigit())
.padding()
两个音符:
-
GIF 动画在显示动画的流畅度方面做得很差,因为由于文件大小限制,我不得不降低帧速率并进行大量压缩。在模拟器或真实设备上看起来很棒
模拟器中的拖动手势感觉很笨拙,但在物理设备上效果非常好。
【讨论】:
Apple 的PageViewController
示例提出的问题(对我而言)比它回答的要多。也许在这个例子之后(我见过的最有启发性的 SwiftUI 例子)它会更清楚。
这太棒了。如何实现自动播放系统?
我发现了一个问题,如果我在滚动视图中有这个问题,如果我的手指在 PagingView 上方,我将无法向上或向下滚动
@FilipeSá 在let content: () -> Content
下添加private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
,然后在PageControl(index: $index, maxIndex: maxIndex)
下添加.onReceive(self.timer) _ in self.index = (self.index + 1) % 3
。
@FilipeSá 更改为 DragGesture(minimumDistance: 30, coordinateSpace: .local)
以启用上下滚动【参考方案2】:
您可以通过以下代码轻松实现此目的
struct ContentView: View
public let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
@State private var selection = 0
/// images with these names are placed in my assets
let images = ["1","2","3","4","5"]
var body: some View
ZStack
Color.black
TabView(selection : $selection)
ForEach(0..<5) i in
Image("\(images[i])")
.resizable()
.aspectRatio(contentMode: .fit)
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.onReceive(timer, perform: _ in
withAnimation
print("selection is",selection)
selection = selection < 5 ? selection + 1 : 0
)
【讨论】:
但你不能把它放在一个导航视图中 当 TabView 放置在 ScrollView 中时,UI 会滞后。我很想听听一些关于我们如何改进它的 cmets?以上是关于SwiftUI 创建以点为指标的图像滑块的主要内容,如果未能解决你的问题,请参考以下文章