I want to achieve this:
Impression Tracking in iOS using SwiftUI.
How do I do it?
I am using List to show the feeds. If the user spent 3 seconds on the card, I need to update the viewed count. I want the feeds the user spent 3 secs. If he scrolls fast, I don't want those feeds. I tried to achieve this way :
struct TestUIList: View {
@ObservedObject var presenter: Presenter
var body: some View {
List{
if #available(iOS 14.0, *) {
LazyVStack {
ForEach(presenter.feeds.indices,id: \.self) { feedIndex in
let feed = presenter.feeds[feedIndex]
CardView(delegate: presenter, feed: feed, index: feedIndex)
}
}
} else {
// Fallback on earlier versions
}
}
}
}
struct CardView: View {
weak var delegate: CardViewToPresenterProtocol?
let feed: Feed
let index: Int
var body: some View{
ZStack{
GeometryReader{ reader in
RoundedRectangle(cornerRadius: 8)
.fill(Color.green)
.valueChanged(value: reader.frame(in: CoordinateSpace.global).maxY, onChange: { _ in
print("onChange")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3, execute: {
let maxY = reader.frame(in: CoordinateSpace.global).maxY
let screenMaxY = UIScreen.main.bounds.maxY
let screenMinY = UIScreen.main.bounds.minY
if !feed.isVisible {
print("\(index) After 3 sec", maxY, screenMaxY)
if (maxY > screenMinY) && (maxY <= screenMaxY) {
print("\(index) cell became visible ")
delegate?.visibilityChanged(visibilityStatus: !feed.isVisible, id: feed.id)
}
}
})
})
.onAppear(perform: {
print("onAppear")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3, execute: {
let maxY = reader.frame(in: CoordinateSpace.global).maxY
let screenMaxY = UIScreen.main.bounds.maxY
let screenMinY = UIScreen.main.bounds.minY
if !feed.isVisible {
print("\(index) After 3 sec", maxY, screenMaxY)
if maxY > screenMinY && maxY <= screenMaxY {
print("\(index) cell became visible")
delegate?.visibilityChanged(visibilityStatus: !feed.isVisible, id: feed.id)
}
})
}
})
VStack {
Text("\(feed.viewedCount) viewed")
.font(.system(size: 12))
Text("reader MaxY = \(reader.frame(in: CoordinateSpace.global).maxY)")
Text("screen maxy = \(UIScreen.main.bounds.maxY)")
Text("screen miny = \(UIScreen.main.bounds.minY)")
}
}
}.frame( height: 200)
.onTapGesture {
print("Card Tapped")
}
}
}
extension View {
/// A backwards compatible wrapper for iOS 14 `onChange`
@ViewBuilder func valueChanged<T: Equatable>(value: T, onChange: @escaping (T) -> Void) -> some View {
if #available(iOS 14.0, *) {
self.onChange(of: value, perform: onChange)
} else {
self.onReceive(Just(value)) { (value) in
onChange(value)
}
}
}
}