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