• SwiftUI 下拉刷新


    import SwiftUI

    struct RefreshScrollView<Content: View>: View {

        @State private var preOffset: CGFloat = 0

        @State private var offset: CGFloat = 0

        @State private var frozen = false

        @State private var rotation: Angle = .degrees(0)

        @State private var updateTime: Date = Date()

        

        var threshold: CGFloat = 70

        @Binding var refreshing: Bool

        let content: Content

        

        init(_ threshold: CGFloat = 70, refreshing: Binding<Bool>, @ViewBuilder content: () -> Content) {

            self.threshold = threshold

            self._refreshing = refreshing

            self.content = content()

        }

        

        var body: some View {

            VStack {

                ScrollView {

                    ZStack(alignment: .top) {

                        MovingPositionView()

                        

                        VStack {

                            self.content

                                .alignmentGuide(.top, computeValue: { _ in

                                    (self.refreshing && self.frozen) ? -self.threshold : 0

                                })

                        }

                        

                        RefreshHeader(height: self.threshold,

                                      loading: self.refreshing,

                                      frozen: self.frozen,

                                      rotation: self.rotation,

                                      updateTime: self.updateTime)

                    }

                }

                .background(FixedPositionView())

                

                .onPreferenceChange(RefreshPreferenceTypes.RefreshPreferenceKey.self) { values in

                    self.calculate(values)

                }

                .onChange(of: refreshing) { refreshing in

                    DispatchQueue.main.async {

                        if !refreshing {

                            self.updateTime = Date()

                        }

                    }

                }

            }

        }

        

        func calculate(_ values: [RefreshPreferenceTypes.RefreshPreferenceData]) {

            DispatchQueue.main.async {

                /// 计算croll offset

                let movingBounds = values.first(where: { $0.viewType == .movingPositionView })?.bounds ?? .zero

                let fixedBounds = values.first(where: { $0.viewType == .fixedPositionView })?.bounds ?? .zero

                

                self.offset = movingBounds.minY - fixedBounds.minY

                

                self.rotation = self.headerRotation(self.offset)

                /// 触发刷新

                if !self.refreshing, self.offset > self.threshold, self.preOffset <= self.threshold {

                    self.refreshing = true

                }

                

                if self.refreshing {

                    if self.preOffset > self.threshold, self.offset <= self.threshold {

                        self.frozen = true

                    }

                } else {

                    self.frozen = false

                }

                

                self.preOffset = self.offset

            }

        }

        

        func headerRotation(_ scrollOffset: CGFloat) -> Angle {

            if scrollOffset < self.threshold * 0.60 {

                return .degrees(0)

            } else {

                let h = Double(self.threshold)

                let d = Double(scrollOffset)

                let v = max(min(d - (h * 0.6), h * 0.4), 0)

                return .degrees(180 * v / (h * 0.4))

            }

        }

        

    //     位置固定不变的view

        struct FixedPositionView: View {

            var body: some View {

                GeometryReader { proxy in

                    Color

                        .clear

                        .preference(key: RefreshPreferenceTypes.RefreshPreferenceKey.self,

                                    value: [RefreshPreferenceTypes.RefreshPreferenceData(viewType: .fixedPositionView, bounds: proxy.frame(in: .global))])

                }

            }

        }

        

    //     位置随着滑动变化的view,高度为0

        struct MovingPositionView: View {

            var body: some View {

                GeometryReader { proxy in

                    Color

                        .clear

                        .preference(key: RefreshPreferenceTypes.RefreshPreferenceKey.self,

                                    value: [RefreshPreferenceTypes.RefreshPreferenceData(viewType: .movingPositionView, bounds: proxy.frame(in: .global))])

                }

                .frame(height: 0)

            }

        }

        

        struct RefreshHeader: View {

            var height: CGFloat

            var loading: Bool

            var frozen: Bool

            var rotation: Angle

            var updateTime: Date

            

            let dateFormatter: DateFormatter = {

                let df = DateFormatter()

                df.dateFormat = "MM月dd日 HH时mm分ss秒"

                return df

            }()

            

            var body: some View {

                HStack(spacing: 20) {

                    Spacer()

                    

                    Group {

                        if self.loading {

                            VStack {

                                Spacer()

                                ActivityRep()

                                Spacer()

                            }

                        } else {

                            Image(systemName: "arrow.down")

                                .resizable()

                                .aspectRatio(contentMode: .fit)

                                .rotationEffect(rotation)

                        }

                    }

                    .frame( height * 0.25, height: height * 0.8)

                    .fixedSize()

                    .offset(y: (loading && frozen) ? 0 : -height)

                    

                    VStack(spacing: 5) {

                        Text("(self.loading ? "正在刷新数据" : "下拉刷新数据")")

                            .foregroundColor(.secondary)

                            .font(.subheadline)

                        

                        Text("(self.dateFormatter.string(from: updateTime))")

                            .foregroundColor(.secondary)

                            .font(.subheadline)

                    }

                    .offset(y: -height + (loading && frozen ? +height : 0.0))

                    

                    Spacer()

                }

                .frame(height: height)

            }

        }

    }

    struct RefreshPreferenceTypes {

        enum ViewType: Int {

            case fixedPositionView

            case movingPositionView

        }

        

        struct RefreshPreferenceData: Equatable {

            let viewType: ViewType

            let bounds: CGRect

        }

        

        struct RefreshPreferenceKey: PreferenceKey {

            static var defaultValue: [RefreshPreferenceData] = []

            

            static func reduce(value: inout [RefreshPreferenceData],

                               nextValue: () -> [RefreshPreferenceData]) {

                value.append(contentsOf: nextValue())

            }

        }

    }

    struct ActivityRep: UIViewRepresentable {

        func makeUIView(context: UIViewRepresentableContext<ActivityRep>) -> UIActivityIndicatorView {

            return UIActivityIndicatorView()

        }

        

        func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityRep>) {

            uiView.startAnimating()

        }

    }

    群号:186052819
  • 相关阅读:
    转 meta标签之详解
    面向过程分析方法和面向对象分析方法区别到底在哪里
    几大开发模型区别与联系
    第6周作业
    第5次作业
    第四次作业
    4.回合制战斗游戏中需要哪些基本的元素或者属性来达到战斗乐趣?
    需求获取常见的方法是进行客户访谈,结合你的实践谈谈会遇到什么问题,你是怎么解决的?
    4.你认为一些军事方面的软件系统采用什么样的开发模型比较合适?
    作业三
  • 原文地址:https://www.cnblogs.com/zuidap/p/15014786.html
Copyright © 2020-2023  润新知