How can I update view when ScrollView scrolling in SwiftUI

345 Views Asked by At

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

There are 1 best solutions below

0
On BEST ANSWER

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:

  • The inner ScrollView has been moved to a separate function called scrollingSignData. This accepts the view width as a parameter.

  • The view width is found by using a GeometryReader around the parent ZStack. This is a better way to find the width of the screen than using UIScreen.main.bounds.width, which is deprecated.

  • The presentation of an individual signData item has also been moved to a separate function called signDataView. This accepts the scaling factor as parameter.

  • You can use the scaling factor any way you like inside the function signDataView. You will see that I have scaled the ZStack containing the two images, so that they are scaled together. The scaling factor is also used to determine the opacity for the border around the image.

  • In order to be able to compute the scaling factor of each item, the function scrollingSignData first determines the footprint for the item by showing it hidden. A hidden view still occupies space in the layout, it is just not visible. The visible version is then shown in an overlay.

  • The overlay for an item contains a GeometryReader, which is used to find the position of the item in the global coordinate space. From this position, it is possible to calculate the distance from the middle of the screen. From this distance, it is possible to calculate a scaling factor.

  • BTW, you could consider using .padding instead of Spacer() with a frame size, it would be simpler. But the code below still uses your original spacing.

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