From ac6aa3f9091118f6b1b0c7ac0d46a684afeaed82 Mon Sep 17 00:00:00 2001 From: tsuzukihashi Date: Mon, 5 Jun 2023 09:07:47 +0900 Subject: [PATCH] feat: Add Onboarding Item --- .../TsuzuKit/Components/SnapCarousel.swift | 79 +++++++++++++++++++ .../Representable/PageControllView.swift | 44 +++++++++++ 2 files changed, 123 insertions(+) create mode 100644 Sources/TsuzuKit/Components/SnapCarousel.swift create mode 100644 Sources/TsuzuKit/Representable/PageControllView.swift diff --git a/Sources/TsuzuKit/Components/SnapCarousel.swift b/Sources/TsuzuKit/Components/SnapCarousel.swift new file mode 100644 index 0000000..493ed72 --- /dev/null +++ b/Sources/TsuzuKit/Components/SnapCarousel.swift @@ -0,0 +1,79 @@ +import SwiftUI + +/** + + */ +public struct SnapCarousel: View { + var content: (T) -> Content + var items: [T] + + // property + var spacing: CGFloat + var trailingSpace: CGFloat + + @Binding var index: Int + + public init( + spacing: CGFloat = 16, + trailingSpace: CGFloat = 100, + index: Binding, + items: [T], + @ViewBuilder content: @escaping (T) -> Content + ) { + self.spacing = spacing + self.trailingSpace = trailingSpace + self._index = index + self.items = items + self.content = content + } + + @GestureState var offset: CGFloat = 0 + @State var currentIndex: Int = 0 + @State var isScroll: Bool = false + + public var body: some View { + GeometryReader { proxy in + let width = proxy.size.width - (trailingSpace - spacing) + let adjustMentWidth = (trailingSpace / 2) - spacing + + HStack(spacing: spacing) { + ForEach(items) { item in + content(item) + .frame(width: proxy.size.width - trailingSpace) + } + } + .padding(.horizontal, spacing) + .offset(x: (CGFloat(currentIndex) * -width) + (currentIndex != 0 ? adjustMentWidth : 0) + offset) + .gesture( + DragGesture() + .updating($offset, body: { value, out, _ in + out = value.translation.width + }) + .onEnded({ value in + let offsetX = value.translation.width + let progress = -offsetX / width + let roundIndex = progress.rounded() + + currentIndex = max(min(currentIndex + Int(roundIndex), items.count - 1), 0) + currentIndex = index + isScroll = false + }) + .onChanged({ value in + isScroll = true + let offsetX = value.translation.width + let progress = -offsetX / width + let roundIndex = progress.rounded() + + index = max(min(currentIndex + Int(roundIndex), items.count - 1), 0) + }) + ) + } + .onChange(of: index, perform: { value in + if !isScroll { + currentIndex = value + } + }) + .animation(.easeInOut, value: offset == 0) + .animation(.easeInOut, value: currentIndex) + } +} diff --git a/Sources/TsuzuKit/Representable/PageControllView.swift b/Sources/TsuzuKit/Representable/PageControllView.swift new file mode 100644 index 0000000..88293dd --- /dev/null +++ b/Sources/TsuzuKit/Representable/PageControllView.swift @@ -0,0 +1,44 @@ +import SwiftUI + +public struct PageControllView: UIViewRepresentable { + @Binding var currentPage: Int + var numberOfPages: Int = 0 + + public init(currentPage: Binding, numberOfPages: Int) { + self._currentPage = currentPage + self.numberOfPages = numberOfPages + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + public func makeUIView(context: Context) -> UIPageControl { + let pageControl = UIPageControl() + pageControl.currentPageIndicatorTintColor = .label + pageControl.numberOfPages = numberOfPages + pageControl.pageIndicatorTintColor = .secondaryLabel + pageControl.addTarget( + context.coordinator, + action: #selector(Coordinator.updateCurrentPage(sender:)), + for: .valueChanged + ) + return pageControl + } + + public func updateUIView(_ uiView: UIPageControl, context: Context) { + uiView.currentPage = currentPage + } + + public class Coordinator: NSObject { + var pageControl: PageControllView + + init(_ pageControl: PageControllView) { + self.pageControl = pageControl + } + + @objc func updateCurrentPage(sender: UIPageControl) { + pageControl.currentPage = sender.currentPage + } + } +}