'Change a property on TabView drag gesture in SwiftUI (View pager)
I have written a generic ViewPager with TabView and it works perfectly. However, I want to pause the timer (auto swipe) when user starts dragging and resume it when user finishes the dragging. Is there anyway to do that?
This is my ViewPager:
struct ViewPager<Data, Content> : View
where Data : RandomAccessCollection, Data.Element : Identifiable, Content : View {
private var timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
@Binding var currentIndex: Int
private let data: [Data.Element]
private let content: (Data.Element) -> Content
private let isTimerEnabled: Bool
private let showIndicator: PageTabViewStyle.IndexDisplayMode
init(_ data: Data,
currentIndex: Binding<Int>,
isTimerEnabled: Bool = false,
showIndicator: PageTabViewStyle.IndexDisplayMode = .never,
@ViewBuilder content: @escaping (Data.Element) -> Content) {
_currentIndex = currentIndex
self.data = data.map { $0 }
self.content = content
self.isTimerEnabled = isTimerEnabled
self.showIndicator = showIndicator
}
private var totalCount: Int {
data.count
}
var body: some View {
TabView(selection: $currentIndex) {
ForEach(data) { item in
self.content(item)
.tag(item.id)
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: showIndicator))
.onReceive(timer) { _ in
if !isTimerEnabled {
timer.upstream.connect().cancel()
} else {
withAnimation {
currentIndex = currentIndex < (totalCount - 1) ? currentIndex + 1 : 0
}
}
}
}
}
Solution 1:[1]
To "pause" while user is dragging, you can exchange .common with .default in the Timer. But what you probably also want is setting the timer back to 2 secs once the dragging is over ...
I got this to work but I use a global var, so the timer stays around and this feels wrong – can someone help further?
// global var – this seems wrong, but works
var timer = Timer.publish(every: 2, on: .main, in: .default).autoconnect()
struct ViewPager<Data, Content> : View
where Data : RandomAccessCollection, Data.Element : Identifiable, Content : View {
@Binding var currentIndex: Int
private let data: [Data.Element]
private let content: (Data.Element) -> Content
private let isTimerEnabled: Bool
private let showIndicator: PageTabViewStyle.IndexDisplayMode
init(_ data: Data,
currentIndex: Binding<Int>,
isTimerEnabled: Bool = false,
showIndicator: PageTabViewStyle.IndexDisplayMode = .never,
@ViewBuilder content: @escaping (Data.Element) -> Content) {
_currentIndex = currentIndex
self.data = data.map { $0 }
self.content = content
self.isTimerEnabled = isTimerEnabled
self.showIndicator = showIndicator
}
private var totalCount: Int {
data.count
}
var body: some View {
TabView(selection: $currentIndex) {
ForEach(data) { item in
self.content(item)
.tag(item.id)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: showIndicator))
.onReceive(timer) { _ in
if !isTimerEnabled {
timer.upstream.connect().cancel()
} else {
print("received")
withAnimation {
currentIndex = currentIndex < (totalCount - 1) ? currentIndex + 1 : 0
print(currentIndex)
}
}
}
.onChange(of: currentIndex) { _ in
timer = Timer.publish(every: 2, on: .main, in: .default).autoconnect()
}
}
}
Solution 2:[2]
same principle but used selection for TabView (and tag for view) and timer as not global var
struct MotivationTabView: View {
// MARK: - PROPERTIES
@State private var selectedItem = "Adolf Dobr’a?sk?j"
@State private var isTimerEnabled: Bool = true
@State private var timer = Timer.publish(every: 10, on: .main, in: .default).autoconnect()
let items: KeyValuePairs = ["Adolf Dobr’a?sk?j": "Svij narod treba ?ubyty i ne ha?byty s’a za ?oho!",
"Fjodor Mychailovi? Dostojevsk?j": "Chto ne maje narod, tot ne maje any Boha! Bu?te sobi ist?, že vš?tk? tot?, što perestanu? rozumity svomu narodu i stra?aju? z nym perevjaza?a, stra?aju? jedno?asno viru otc’ovsku, stavaju? bu? ateistamy, abo cholodn?ma.",
"Pau del Rosso": "Je barz važn?m uchovaty sobi vlastnu identi?nos?. To naš unikatn?j dar pro druh?ch, unikatn?j v cilim kozmosi.",
"Viktor Hugo": "Velykos? naroda ne mir’a? s’a ki?kos?ov, tak jak i velykos? ?olovika ne mir’a? s’a v?škov.",
"Lewis Lapham":"Strata identit? je v?hoda pro biznis... pok?a b? jem znav, chto jem, ?om b? jem si bezprestajno kupovav nov? zna?k? vod? po holi?u?"]
private var totalCount: Int {
items.count
}
private func nextItem(currItem: String) -> String{
switch currItem {
case "Adolf Dobr’a?sk?j": return "Fjodor Mychailovi? Dostojevsk?j"
case "Fjodor Mychailovi? Dostojevsk?j": return "Pau del Rosso"
case "Pau del Rosso": return "Viktor Hugo"
case "Viktor Hugo": return "Lewis Lapham"
default: return "Adolf Dobr’a?sk?j"
}
}
// MARK: - BODY
var body: some View {
GroupBox {
TabView(selection: $selectedItem){
ForEach(items, id: \.self.key) { item in
VStack(alignment: .trailing){
Text(item.value)
Text(item.key)
.font(.caption)
.padding(.top, 5)
}.tag(item.key)
}
} //: TABS
.tabViewStyle(PageTabViewStyle())
.frame(height: 240)
.onReceive(timer) { _ in
if !isTimerEnabled {
timer.upstream.connect().cancel()
} else {
print("received")
withAnimation() {
selectedItem = nextItem(currItem: selectedItem)
print(selectedItem)
}
}
}
.onAppear{
isTimerEnabled = true
timer = Timer.publish(every: 10, on: .main, in: .default).autoconnect()
}
.onDisappear{
isTimerEnabled = false
}
} //: BOX
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | ChrisR |
| Solution 2 | Peter |
