swiftswiftuiswiftui-animationswiftui-scrollview

How can I update view when ScrollView scrolling in SwiftUI


I'm new to SwiftUI and I'm trying to update the view while ScrollView is scrolling. As I scroll the screen, I want to increase the size of the image in the middle by 1.2, increase the visibility of the tick image and add a border to the image.

I am trying to achieve this

And this is what I have so far

my code :

struct SULoveCompatibilityInputView: View, HoroscopesProtocol {
    @Environment(\.presentationMode) var presentationMode
    var viewModel = LoveCompatibilityViewModel()
    @State var horoscopeInfo: [LoveCompatibilityHoroscopeData] = []
    var horoscopeViews: [FriendHoroscopeInfoView] = []
    let screenWidth = UIScreen.main.bounds.width
    let itemWidth: CGFloat = 110
    let spacing: CGFloat =  ((UIScreen.main.bounds.width/2) - 125)
    var body: some View {
        NavigationView {
            ZStack{
                Color(ThemeManager.pageBackgroundColor()).ignoresSafeArea()
                ScrollView{
                    VStack{
                        Spacer()
                            .frame(height: 20)
                        UserHoroscopeInfoView()
                        Spacer().frame(height: 45)
                        FriendHoroscopeInfoView()
                            .padding()
                        Spacer().frame(height: 5)
                        ScrollView(.horizontal, showsIndicators: false){
                            HStack(spacing: spacing){
                                Spacer()
                                    .frame(width: 70)
                                ForEach(horoscopeInfo, id: \.id) { signData in
                                    VStack{
                                        Spacer()
                                            .frame(height: 20)
                                        ZStack(alignment: .topTrailing){
                                            Image(signData.signIcon ?? "virgo")
                                                .resizable()
                                                .aspectRatio(contentMode: .fit)
                                                .frame(width: 110, height: 110)
                                            Image("popupTik")
                                                .resizable()
                                                .aspectRatio(contentMode: .fit)
                                                .frame(width: 18, height: 18)
                                        }

                                        Spacer().frame(height: 20)
                                        Text(signData.signName ?? "Burç")
                                            .font(.system(size: 16).bold())
                                            .foregroundColor(Color(ThemeManager.textColor()))
                                        Spacer().frame(height: 5)
                                        Text(signData.signDateStart ?? "")
                                            .font(.system(size: 14, weight: .medium))
                                            .foregroundColor(Color(ThemeManager.textColor()))
                                        Spacer().frame(height: 2)
                                        Text(signData.signDateStart ?? "")
                                            .font(.system(size: 14, weight: .medium))
                                            .foregroundColor(Color(ThemeManager.textColor()))
                                        
                                    }      
                                }
                                Spacer()
                                    .frame(width: 70)
                            }
                            .padding(.top, 10)
                            .padding(.bottom, 15)
                        }
                        .onAppear{
                            viewModel.horoscopes = self
                            viewModel.getHoroscopes()
                        }
                    }
                }
                .navigationTitle("horoscopeCompatibilityTitle".localized)
                .navigationBarItems(leading: Button(action: {
                    print("geri tuşuna basıldı")
                    presentationMode.wrappedValue.dismiss()
                }) {
                    Image(systemName: "chevron.left")
                })
            }
        }
    }
    func fillFriendSignScroll(data: [LoveCompatibilityHoroscopeData]) {
        horoscopeInfo = data
    }
}

Solution

  • One way to get this effect is to use a GeometryReader to determine how far the scrolled item is from the middle of the screen. When it is near to the middle, a scale effect can be applied and a border can be shown around the image.

    The code below is an adaption of your example which shows this working. Some notes:

    So here you go, hope it helps:

    struct SULoveCompatibilityInputView: View, HoroscopesProtocol {
        @Environment(\.presentationMode) var presentationMode
        @State var viewModel = LoveCompatibilityViewModel()
        @State var horoscopeInfo: [LoveCompatibilityHoroscopeData] = []
        var horoscopeViews: [FriendHoroscopeInfoView] = []
        let itemWidth: CGFloat = 110
        
        private func signDataView(signData: LoveCompatibilityHoroscopeData, scalingFactor: CGFloat = 1.0) -> some View {
            VStack{
                Spacer()
                    .frame(height: 20)
                ZStack(alignment: .topTrailing){
                    Image(signData.signIcon ?? "virgo")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: itemWidth, height: itemWidth)
                        .overlay {
                            Circle()
                                .stroke(lineWidth: 4)
                                .foregroundColor(.purple)
                                .opacity((scalingFactor - 1) / 0.2)
                        }
                    Image("popupTik")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 18, height: 18)
                }
                .scaleEffect(scalingFactor)
                
                Spacer().frame(height: 20)
                Text(signData.signName ?? "Burç")
                    .font(.system(size: 16).bold())
                    .foregroundColor(Color(ThemeManager.textColor()))
                Spacer().frame(height: 5)
                Text(signData.signDateStart ?? "")
                    .font(.system(size: 14, weight: .medium))
                    .foregroundColor(Color(ThemeManager.textColor()))
                Spacer().frame(height: 2)
                Text(signData.signDateStart ?? "")
                    .font(.system(size: 14, weight: .medium))
                    .foregroundColor(Color(ThemeManager.textColor()))
                
            }
        }
        
        private func scrollingSignData(viewWidth: CGFloat) -> some View {
            let midX = viewWidth / 2
            let spacing: CGFloat = midX - 125
            let halfItemWidth = itemWidth / 2
            return ScrollView(.horizontal, showsIndicators: false){
                HStack(spacing: spacing){
                    Spacer()
                        .frame(width: 70)
                    ForEach(horoscopeInfo, id: \.id) { signData in
                        signDataView(signData: signData)
                            .hidden()
                            .overlay {
                                GeometryReader { proxy in
                                    let pos = proxy.frame(in: .global).midX
                                    let dx = min(halfItemWidth, abs(midX - pos))
                                    let proximity = min(1.0, ((halfItemWidth - dx) * 2) / halfItemWidth)
                                    let scalingFactor = 1.0 + (proximity * 0.2)
                                    signDataView(signData: signData, scalingFactor: scalingFactor)
                                        .fixedSize()
                                }
                            }
                    }
                    Spacer()
                        .frame(width: 70)
                }
                .padding(.top, 10)
                .padding(.bottom, 15)
            }
        }
        
        var body: some View {
            NavigationView {
                GeometryReader { proxy in
                    ZStack{
                        Color(ThemeManager.pageBackgroundColor()).ignoresSafeArea()
                        ScrollView{
                            VStack{
                                Spacer()
                                    .frame(height: 20)
                                UserHoroscopeInfoView()
                                Spacer().frame(height: 45)
                                FriendHoroscopeInfoView()
                                    .padding()
                                Spacer().frame(height: 5)
                                scrollingSignData(viewWidth: proxy.size.width)
                                    .onAppear{
                                        viewModel.horoscopes = self
                                        viewModel.getHoroscopes()
                                    }
                            }
                        }
                        .navigationTitle("horoscopeCompatibilityTitle") // .localized
                        .navigationBarItems(leading: Button(action: {
                            print("geri tuşuna basıldı")
                            presentationMode.wrappedValue.dismiss()
                        }) {
                            Image(systemName: "chevron.left")
                        })
                    }
                }
            }
        }
        
        func fillFriendSignScroll(data: [LoveCompatibilityHoroscopeData]) {
            horoscopeInfo = data
        }
    }
    

    Animation