Dev-iOS/UI

[UI] 스크롤 시 상단 탑 SearchBar 이동 시키기 - (SwiftUI 2.0 - iOS 14 이상)

lafortune 2023. 7. 20. 15:58
반응형

 

struct StickyTopSearchBar: View {
    @State private var searchQuery = ""
    @State private var offset: CGFloat = 0
    @State private var startOffset: CGFloat = 0
    @State private var titleOffset: CGFloat = 0
    @State private var titleBarHeight: CGFloat = 0

    var body: some View {
        ZStack(alignment: .top) {
            VStack {
                if searchQuery == "" {
                    HStack {
                        Button {

                        } label: {
                            Image(systemName: "person")
                                .font(.title2)
                                .foregroundColor(.primary)
                        }

                        Spacer()

                        Button {

                        } label: {
                            Image(systemName: "plus")
                                .font(.title2)
                                .foregroundColor(.primary)
                        }
                    }
                    .padding()

                    HStack {
                        Text("Home")
                            .fontWeight(.bold)
                            .foregroundColor(.primary)
                        .font(.largeTitle)
                        .overlay(
                            GeometryReader { reader -> Color in
                                let width = reader.frame(in: .global).maxX

                                DispatchQueue.main.async {
                                    if titleOffset == 0 {
                                        titleOffset = width
                                    }
                                }

                                return Color.clear
                            }
                            .frame(width: 0, height: 0)
                        )
                        .padding()
                        .scaleEffect(getScale())
                        .offset(getOffset())

                        Spacer()

                    }
                }

                VStack {
                    HStack(spacing: 15) {
                        Image(systemName: "magnifyingglass")
                            .font(.system(size: 23, weight: .bold))
                            .foregroundColor(.gray)

                        TextField("Search", text: $searchQuery)
                    }
                    .padding(.vertical, 10)
                    .padding(.horizontal)
                    .background(Color.primary.opacity(0.05))
                    .cornerRadius(8)
                    .padding(.horizontal)

                    if searchQuery == "" {
                        HStack {
                            Rectangle()
                                .fill(Color.gray.opacity(0.06))
                                .frame(height: 0.5)

                        }
                        .padding()
                    }
                }
                .offset(y: offset > 0 && searchQuery == "" ? (offset <= 95 ? -offset : -95) : 0)
            }
            .zIndex(1)
            .padding(.bottom, getOffset().height)
            .background(Color.white.ignoresSafeArea())
            .overlay(
                GeometryReader { reader -> Color in
                    let height = reader.frame(in: .global).maxY

                    DispatchQueue.main.async {
                        if titleBarHeight == 0 {
                            titleBarHeight = height - (UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
                        }
                    }

                    return Color.clear
                }
            )
            .animation(.easeInOut, value: searchQuery != "")

            ScrollView(.vertical, showsIndicators: false) {
                VStack(spacing: 10) {
                    ForEach(1..<15, id: \.self) { num in
                        ZStack {
                            Rectangle()
                                .foregroundColor(.blue)
                                .frame(height: 150)
                                .frame(maxWidth: .infinity)
                                .padding(10)

                            Text("\(num)")
                        }
                    }
                }
                .padding(.top, 10)
                .padding(.top, searchQuery == "" ? titleBarHeight : 90)
                .overlay(
                    GeometryReader { proxy -> Color in
                        let minY = proxy.frame(in: .global).minY

                        DispatchQueue.main.async {
                            /// to get original offset
                            /// ie from 0
                            /// just minus start offset
                            if startOffset == 0 {
                                startOffset = minY
                            }
                            offset = startOffset - minY
                        }

                        return Color.clear
                    }
                    , alignment: .top
                )
            }
        }
    }

    func getOffset() -> CGSize {
        var size: CGSize = .zero
        let screenWidth = UIScreen.main.bounds.width / 2
        let offsetForCalculation = min(max(offset, 0), 75)
        size.width = min(offsetForCalculation * 2, screenWidth - titleOffset)
        size.height = -offsetForCalculation
        return size
    }

    func getScale() -> CGFloat {
        if offset > 0 {
            let screenWidth = UIScreen.main.bounds.width
            let progress = 1 - (getOffset().width / screenWidth)
            return max(progress, 0.9)
        } else {
            return 1
        }
    }
}
반응형