iOS 中的印象跟踪

问题描述 投票:0回答:2

我想实现这个:

使用 SwiftUI 在 iOS 中进行

印象跟踪

我该怎么做?

我正在使用列表来显示提要。如果用户在卡片上花费了 3 秒,我需要更新查看次数。我想要用户花费 3 秒的提要。如果他滚动得很快,我就不需要那些提要。我尝试以这种方式实现:

    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)
            }
        }
    }
}

ios swift tracking swiftui-list impressions
2个回答
0
投票

对于这个问题:

  1. 我们需要 UI 中可见的内容;通过使用Geometry reader,我们可以获得可见的单元格
  2. 获取可见单元格后,我们需要添加一个计时器,因为我们希望用户在可见单元格上花费了一些时间;在我的例子中添加了一个五秒计时器
  3. 当用户停止滚动时计时器启动;如果它们滚动,计时器将失效(停止)
  4. 当滚动视图完成滚动时检查此链接 SwiftUI - 检测 ScrollView 何时完成滚动?
  5. 计时器完成五秒后,使用委托和协议来更新 feed 浏览次数

检查下面的代码

型号

struct Feed: Identifiable {
    var id = UUID()
    var viewedCount: Int = 0
    var viewed = false 
     func dummyArray() -> [Feed] {
         var array = [Feed]()
         for _ in 1...100 {
             array.append(Feed())
         }
         return array
    }
}

动态列表视图

struct TestScrollView: View {
    @ObservedObject var presenter: Presenter
    @State private var scrolling: Bool = false
    let detector: CurrentValueSubject<CGFloat, Never>
    let publisher: AnyPublisher<CGFloat, Never>
   
     init(presenter: Presenter) {
        self.presenter = presenter
        let detector = CurrentValueSubject<CGFloat, Never>(0)
        self.publisher = detector
            .debounce(for: .seconds(0.2), scheduler: DispatchQueue.main)
            .dropFirst()
            .eraseToAnyPublisher()
        self.detector = detector
    }
    var body: some View {
    ScrollView{
        GeometryReader { reader in
            Rectangle()
                .frame(width: 0, height: 0)
                .valueChanged(value: reader.frame(in: .global).origin.y) { offset in
                    if !scrolling {
                        scrolling = true
                        print("scrolling")
                        presenter.isScrolling = true
                    }
                    detector.send(offset)
                }
                .onReceive(publisher) { _ in
                    scrolling = false
                    print("not scrolling")
                    presenter.isScrolling = false
                }
        }
        ZStack {
            LazyVStack{
                ForEach(presenter.feeds.indices, id: \.self) { 
                    feedIndex in
                        let feed = presenter.feeds[feedIndex]
                        ScrollCardView(feed: feed, index: feedIndex,delegate: presenter,isScrolling: $isScrolling)
                    } 

                    Spacer()
                }.padding([.leading, .trailing,.top], 10)
            }
        }
    }
}

卡片视图

struct ScrollCardView: View {
    let feed: Feed
    let index: Int
    weak var delegate: CardViewToPresenterProtocol?
    @State private var timer: Timer?
    @Binding var isScrolling: Bool
    @State var maxY: CGFloat = 0
    
    var body: some View {
        ZStack{
            RoundedRectangle(cornerRadius: 8)
                .fill(Color.green)
                
            VStack(alignment: .leading) {
                Text("feed \(index)")
                Text("reader maxY: \(maxY)")
                Text("screen maxY: \(UIScreen.main.bounds.maxY)")
            }
        }.frame(height: 200)
            .onDisappear(perform: {
                self.timer?.invalidate()
            })
            .background(GeometryReader{ reader in
                Color.clear
                    .onAppear(perform: {
                        print("onAppear")
                        self.maxY = reader.frame(in: CoordinateSpace.global).maxY
                        let screenMaxY = UIScreen.main.bounds.maxY
                        if isScrolling == false {
                            executeSomeTask(screenMaxY: screenMaxY)
                        }else {
                    timer?.invalidate()
                }
                    })
                    .valueChanged(value: isScrolling, onChange: { value in
                        let screenMaxY = UIScreen.main.bounds.maxY
                        if value == false {
                            executeSomeTask(screenMaxY: screenMaxY)
                        }else {
                            timer?.invalidate()
                        }
                    })
                    .onReceive(Just(reader.frame(in: CoordinateSpace.global).maxY)) { value in
                        self.maxY = value
                    }
            })
    }
    

    private func executeSomeTask(screenMaxY: CGFloat) {
        if (maxY > 200) && (maxY <= screenMaxY) && !(feed.viewed) {
            timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false, block: { _ in
                print(maxY, screenMaxY)
                if (maxY > 200) && (maxY <= screenMaxY) {
                    print(maxY, screenMaxY)
                    if !(feed.viewed) {
                        self.delegate?.visibilityChanged(visibilityStatus: true, index: index)
                    }
                }else {
                    timer?.invalidate()
                }
            })
        }else{
            timer?.invalidate()
        }
    }
}

演讲者班

class Presenter: ObservableObject,CardViewToPresenterProtocol {
    
    @Published var feeds = Feed().dummyArray()
    @Published var isScrolling: Bool = false
    @Published  var visibleRows = [Int: Bool]()
   
     func visibilityChanged(visibilityStatus: Bool,index: Int) {
        self.feeds[index].viewed = visibilityStatus
        if visibleRows[index] == nil {
            self.visibleRows[index] = visibilityStatus
        }
        print("visible rows",visibleRows)
        Task {
            let finishedApiCall = await self.apiCall(feeds: Array(visibleRows.keys))
            print("After api call", finishedApiCall)
        }
    }
    //performing some network operation
    func apiCall(feeds: [Int]) async -> [Int] {
        //Do required stuff here
    }
}

/// 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)
            }
        }
    }

0
投票

我编写了一个用于印象跟踪的库。 希望有帮助。

https://github.com/623637646/ImpressionKit

© www.soinside.com 2019 - 2024. All rights reserved.