Last active
June 28, 2023 09:57
-
-
Save Codelaby/bd5a53d5ab7bbcb0550bdd4d0dc4977a to your computer and use it in GitHub Desktop.
Ejemplos de TabView en SwiftUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CarouselView.swift | |
// Avatars | |
// | |
// Created by Codelaby on 28/6/23. | |
// | |
/* | |
https://swiftuilibrary.com/components/paginated-carousel | |
https://github.com/manuelduarte077/CustomCarouselList | |
https://stackoverflow.com/questions/75049237/slide-carousel-cards-on-cards-tap-in-swiftui | |
https://stackoverflow.com/questions/62864221/change-tabview-indicator-color-swiftui | |
https://www.appcoda.com/swiftui-tabview-paged-scrolling/ | |
https://betterprogramming.pub/swipe-the-page-with-swiftuipager-981e379e3bbd | |
https://gist.github.com/beader/e1312aa5b88af30407bde407235fbe67 | |
https://www.boltuix.com/2022/08/how-to-create-onboarding-with-swiftui.html | |
*/ | |
import SwiftUI | |
struct ItemCard: Identifiable, Hashable { | |
let id = UUID() | |
let title: String | |
let color: Color | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(id) | |
} | |
static func ==(lhs: ItemCard, rhs: ItemCard) -> Bool { | |
return lhs.id == rhs.id | |
} | |
} | |
struct CarouselView: View { | |
@State private var selectedPage = 0 | |
var items: [ItemCard] = [] | |
init() { | |
let colors: [Color] = [.orange, .green, .yellow, .teal, .red, .blue, .purple, .gray, .brown, .cyan, .mint, .pink] | |
let emojis: [String] = ["🐀", "🐂", "🐅", "🐇", "🐉", "🐍", "🐎", "🐐", "🐒", "🐓", "🐕", "🐖"] | |
for index in 0..<emojis.count { | |
let item = ItemCard(title: emojis[index], color: colors[index]) | |
items.append(item) | |
} | |
//UIPageControl.appearance().currentPageIndicatorTintColor = .red | |
//UIPageControl.appearance().pageIndicatorTintColor = UIColor.black.withAlphaComponent(0.2) | |
} | |
var body: some View { | |
VStack { | |
HStack() { | |
Button("<") { if selectedPage > 0 { | |
withAnimation { selectedPage -= 1 } | |
}} | |
Spacer().frame(width: 40) | |
Button(">") { if (selectedPage < (items.count - 1)) { | |
withAnimation { selectedPage += 1 } | |
}} | |
} | |
TabView(selection: $selectedPage) { | |
ForEach(items.indices, id: \.self) { index in | |
Text(items[index].title) | |
.font(.system(size: 96)) | |
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 250) | |
.background(items[index].color) | |
.clipShape(RoundedRectangle(cornerRadius: 32)) | |
.padding(.horizontal, 16) | |
.tabItem { | |
Text(items[index].title) | |
} | |
.tag(index) | |
} | |
} | |
.tabViewStyle(PageTabViewStyle()) | |
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) | |
//.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 340) | |
.border(.purple) | |
.onChange(of: selectedPage) { newValue in | |
print("New page: \(newValue)") | |
} | |
} | |
} | |
} | |
struct CarouselView_Previews: PreviewProvider { | |
static var previews: some View { | |
CarouselView() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CarouselView.swift | |
// Avatars | |
// | |
// Created by Codelaby on 28/6/23. | |
// | |
/* | |
https://swiftuilibrary.com/components/paginated-carousel | |
https://github.com/manuelduarte077/CustomCarouselList | |
https://stackoverflow.com/questions/75049237/slide-carousel-cards-on-cards-tap-in-swiftui | |
https://stackoverflow.com/questions/62864221/change-tabview-indicator-color-swiftui | |
https://www.appcoda.com/swiftui-tabview-paged-scrolling/ | |
https://betterprogramming.pub/swipe-the-page-with-swiftuipager-981e379e3bbd | |
https://gist.github.com/beader/e1312aa5b88af30407bde407235fbe67 | |
https://www.boltuix.com/2022/08/how-to-create-onboarding-with-swiftui.html | |
*/ | |
import SwiftUI | |
struct ItemCard: Identifiable, Hashable { | |
let id = UUID() | |
let title: String | |
let color: Color | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(id) | |
} | |
static func ==(lhs: ItemCard, rhs: ItemCard) -> Bool { | |
return lhs.id == rhs.id | |
} | |
} | |
struct InfiniteTabPageView<Content: View>: View { | |
@GestureState private var translation: CGFloat = .zero | |
@State private var currentPage: Int = 0 | |
@State private var offset: CGFloat = .zero | |
private let width: CGFloat | |
private let animationDuration: CGFloat = 0.25 | |
let content: (_ page: Int) -> Content | |
init(width: CGFloat = 390, @ViewBuilder content: @escaping (_ page: Int) -> Content) { | |
self.width = width | |
self.content = content | |
} | |
private var dragGesture: some Gesture { | |
DragGesture(minimumDistance: 0) | |
.updating($translation) { value, state, _ in | |
let translation = min(width, max(-width, value.translation.width)) | |
state = translation | |
} | |
.onEnded { value in | |
offset = min(width, max(-width, value.translation.width)) | |
let predictEndOffset = value.predictedEndTranslation.width | |
withAnimation(.easeOut(duration: animationDuration)) { | |
if offset < -width / 2 || predictEndOffset < -width { | |
offset = -width | |
} else if offset > width / 2 || predictEndOffset > width { | |
offset = width | |
} else { | |
offset = 0 | |
} | |
} | |
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) { | |
if offset < 0 { | |
currentPage += 1 | |
} else if offset > 0 { | |
currentPage -= 1 | |
} | |
offset = 0 | |
} | |
} | |
} | |
var body: some View { | |
ZStack { | |
content(pageIndex(currentPage + 2) - 1) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.offset(x: CGFloat(1 - offsetIndex(currentPage - 1)) * width) | |
content(pageIndex(currentPage + 1) + 0) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.offset(x: CGFloat(1 - offsetIndex(currentPage + 1)) * width) | |
content(pageIndex(currentPage + 0) + 1) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.offset(x: CGFloat(1 - offsetIndex(currentPage)) * width) | |
} | |
.contentShape(Rectangle()) | |
.offset(x: translation) | |
.offset(x: offset) | |
.gesture(dragGesture) | |
.clipped() | |
} | |
private func pageIndex(_ x: Int) -> Int { | |
// 0 0 0 3 3 3 6 6 6 . . . 周期函数 | |
// 用来决定 3 个 content 分别应该展示第几页 | |
Int((CGFloat(x) / 3).rounded(.down)) * 3 | |
} | |
private func offsetIndex(_ x: Int) -> Int { | |
// 0 1 2 0 1 2 0 1 2 ... 周期函数 | |
// 用来决定静止状态 3 个 content 的摆放顺序 | |
if x >= 0 { | |
return x % 3 | |
} else { | |
return (x + 1) % 3 + 2 | |
} | |
} | |
} | |
struct CarouselView: View { | |
@State private var selectedPage = 0 | |
var items: [ItemCard] = [] | |
init() { | |
let colors: [Color] = [.orange, .green, .yellow, .teal, .red, .blue, .purple, .gray, .brown, .cyan, .mint, .pink] | |
let emojis: [String] = ["🐀", "🐂", "🐅", "🐇", "🐉", "🐍", "🐎", "🐐", "🐒", "🐓", "🐕", "🐖"] | |
for index in 0..<emojis.count { | |
let item = ItemCard(title: emojis[index], color: colors[index]) | |
items.append(item) | |
} | |
//UIPageControl.appearance().currentPageIndicatorTintColor = .red | |
//UIPageControl.appearance().pageIndicatorTintColor = UIColor.black.withAlphaComponent(0.2) | |
} | |
var body: some View { | |
VStack { | |
GeometryReader { geometry in | |
InfiniteTabPageView(width: geometry.size.width) { page in | |
let index = (page % items.count + items.count) % items.count | |
Text(items[index].title) | |
.font(.system(size: 96)) | |
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 250) | |
.background(items[index].color) | |
.clipShape(RoundedRectangle(cornerRadius: 32)) | |
.padding(.horizontal, 16) | |
.tabItem { | |
Text(items[index].title) | |
} | |
.onTapGesture { | |
selectedPage = index | |
} | |
} | |
.border(.purple) | |
.onChange(of: selectedPage) { newValue in | |
print("New page: \(newValue)") | |
} | |
} | |
} | |
} | |
} | |
struct CarouselView_Previews: PreviewProvider { | |
static var previews: some View { | |
CarouselView() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CarouselView.swift | |
// Avatars | |
// | |
// Created by Codelaby on 28/6/23. | |
// | |
/* | |
https://swiftuilibrary.com/components/paginated-carousel | |
https://github.com/manuelduarte077/CustomCarouselList | |
https://stackoverflow.com/questions/75049237/slide-carousel-cards-on-cards-tap-in-swiftui | |
https://stackoverflow.com/questions/62864221/change-tabview-indicator-color-swiftui | |
https://www.appcoda.com/swiftui-tabview-paged-scrolling/ | |
https://betterprogramming.pub/swipe-the-page-with-swiftuipager-981e379e3bbd | |
https://gist.github.com/beader/e1312aa5b88af30407bde407235fbe67 | |
https://www.boltuix.com/2022/08/how-to-create-onboarding-with-swiftui.html | |
*/ | |
import SwiftUI | |
struct ItemCard: Identifiable, Hashable { | |
let id = UUID() | |
let title: String | |
let color: Color | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(id) | |
} | |
static func ==(lhs: ItemCard, rhs: ItemCard) -> Bool { | |
return lhs.id == rhs.id | |
} | |
} | |
struct CarouselView: View { | |
@State private var selectedPage = 0 | |
var items: [ItemCard] = [] | |
init() { | |
let colors: [Color] = [.orange, .green, .yellow, .pink, .purple, .blue, .red, .gray, .brown, .cyan, .brown, .teal] | |
let emojis: [String] = ["🐀", "🐂", "🐅", "🐇", "🐉", "🐍", "🐎", "🐐", "🐒", "🐓", "🐕", "🐖"] | |
for index in 0..<emojis.count { | |
let item = ItemCard(title: emojis[index], color: colors[index]) | |
items.append(item) | |
} | |
//UIPageControl.appearance().currentPageIndicatorTintColor = .red | |
//UIPageControl.appearance().pageIndicatorTintColor = UIColor.black.withAlphaComponent(0.2) | |
} | |
var body: some View { | |
VStack { | |
TabView(selection: $selectedPage) { | |
ForEach(items.indices, id: \.self) { index in | |
Text(items[index].title) | |
.font(.system(size: 96)) | |
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 250) | |
.background(items[index].color) | |
.clipShape(RoundedRectangle(cornerRadius: 32)) | |
.padding(.horizontal, 16) | |
.tabItem { | |
Text(items[index].title) | |
} | |
.tag(index) | |
} | |
} | |
.tabViewStyle(PageTabViewStyle()) | |
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) | |
//.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 340) | |
.border(.purple) | |
.onChange(of: selectedPage) { newValue in | |
print("New page: \(newValue)") | |
} | |
} | |
} | |
} | |
struct CarouselView_Previews: PreviewProvider { | |
static var previews: some View { | |
CarouselView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment