SwiftUI unwanted movement in .animation - swiftui

Hello and thanks for reading my post.
My problem is following:
When I start an try to do an loading indicator animation there is an unwanted movement.
The Circle should stay in Place and rotate (like a indicator should do), but in my case it move slightly upwards and resets at the end of animation and starts to move upward again.
It makes absolutely no sense to me!
If there is any solution, I will try it!
It looks like this:
Here is my Code:
ZStack {
Circle()
.stroke(Color(.systemGray5), lineWidth: 40)
.frame(width: 250, height: 250)
.animation(nil)
Circle()
.trim(from: 0, to: 0.2)
.stroke(Color.gray, lineWidth: 40)
.frame(width: 250, height: 250)
.rotationEffect(Angle(degrees: isCircleRotating ? 360 : 0))
//I guess this is where the problem happens
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
.onAppear() {
self.isCircleRotating = true
}
}

Okay so I thought about the animation in another way. The loading screen is during a network call. I outsourced my network call in an own function. Before that, the network call was was in the init of my Extension View. I now have a bool "isLoading" which calls if the call is loading or not. I wrapped my Stack with the Circles in a If condition with the isLoading Bool and I don't know why, but it worked and I'm happy now.

Related

In SwiftUI, how do I initialize an animated value based on the screen height?

I am new to SwiftUI but a very experienced developer (which may even be a hindrance here). I have a full-screen, highly graphical app/game. One of the requirements is for a specific image to animate by floating up and down. Doing this is easy:
#State var y : CGFloat = 400
var rockFloatExtent: CGFloat = 140
var body: some View {
VStack {
GeometryReader { geometry in
Image("Earth1Rocks2")
.resizable()
.scaledToFit()
.position(x: geometry.size.width / 2, y: self.y)
.animation(Animation.easeInOut(duration: 5.0).repeatForever(autoreverses: true), value: self.y)
.onAppear {
self.y = geometry.size.height * percentRockStartHeight + rockFloatExtent
}
}
...
The problem is the the initial value is going to depend on the state of the game. Sometimes, the y value may be 25% of the screen height (again, full-screen app) and other times it may start at 50% of screen height. At least in the emulator UIScreen.main.bounds.size.height seems to be simply wrong with respect to the device height - it doesn't agree with the GeometryReader at all. I would love an easy way to init the value OR some trick with the animation to make this work.
I have tried doing an .onAppear with an initial value and, after a very brief pause, a second value. But the animation just ignores the first value or varies between all three. I have tried initializing the position using GeometryReader but then the element's y value isn't tied to self.y and the animation breaks. I don't know how to reference (and animate) the actual position of the element either without a state variable as an intermediary.
Any help would be greatly appreciated!
Much simpler - just switch frame alignment and everything else will be done by SwiftUI:
#State private var isAnimating = false // << state !!
var body: some View {
Image(systemName: "globe") // << just for demo
.resizable().scaledToFit()
.frame(maxHeight: .infinity, alignment: isAnimating ? .top : .bottom) // << switch !!
.animation(Animation.easeInOut(duration: 5.0).repeatForever(autoreverses: true), value: isAnimating)
.onAppear {
isAnimating = true // << activate !!
}
}
Tested with Xcode 13.4 / iOS 15.5
So as you have a game the phone is potentially landscape mode. If so UIScreen.main.bounds.size.height will give you the height of the phone and not the height of the screen, but the width of the screen. UIScreen.main.bounds.size.height does not react on device rotation. It stays the same.
Maybe that the issue you're facing.
Hope that helps.
The solution of #Asperi looks good to me.

.contentShape losing effect when overlayed

I'm finding .contentShape isn't working the same when it is on top of other shapes that also accept gesture interactions.
Take the following example code:
ZStack {
// Circle()
// .offset(y: -200)
// .gesture(TapGesture().onEnded { print("TAPPED CIRCLE") } )
let path = Path() { path in
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: 1000, y: 1000))
}
path
.stroke(Color.blue, style: StrokeStyle(lineWidth: 2))
.contentShape(path.stroke(style: StrokeStyle(lineWidth: 10)))
.gesture(TapGesture().onEnded { print("TAPPED PATH") })
}
With this code, as is, the contentShape on the path will make taps recognized from a significant distance.
Uncommenting the code for the Circle, suddenly the contentShape doesn't have the same effect and now taps need to be exactly on the path for them to work.
Why is this happening? Can I somehow get the same behaviour even when the "Circle code" is present?
I did an extra little bit of debugging and realized it was working fine. The behaviour is different, though, depending on whether the circle is behind or not.
Here is my extra "debugging":
ZStack {
Circle()
.offset(y: -200)
.gesture(TapGesture().onEnded { print("TAPPED CIRCLE") } )
let path = Path() { path in
path.move(to: CGPoint(x: 250, y: 0))
path.addLine(to: CGPoint(x: 250, y: 1000))
}
path
.stroke(Color.green, style: StrokeStyle(lineWidth: 40))
path
.stroke(Color.blue, style: StrokeStyle(lineWidth: 2))
.contentShape(path.stroke(style: StrokeStyle(lineWidth: 40)))
.gesture(TapGesture().onEnded { print("TAPPED PATH") })
}
All this is doing is adding an extra path, drawing with a stroke the same lineWidth as the contentShape so I can see where the clicks are changing from registering and not registering on the path.
Where the path is over the circle, the hit detection is actually better than where it is not.
It seems that when there isn't a shape underneath, also accepting gesture interaction, there is an additional buffer to where the gestures are recognized.
Losing the buffer, and hence changing the behaviour, is what was making me think there was a problem.

how can I make onTapGesture works only if user tap on circle not inside all frame of Circle in SwiftUI? [duplicate]

This question already has an answer here:
SwiftUI: How to make entire shape recognize gestures when stroked?
(1 answer)
Closed 2 years ago.
I am using this down code for reading tap of user on Circle, the problem is here that it works on all part of frame 100X100 but I expect that it should work only on color filled Circle, how can I solve this issue?
struct ContentView: View {
var body: some View {
Circle()
.fill(Color.red)
.frame(width: 100, height: 100, alignment: .center)
.onTapGesture {
print("tap")
}
}
}
A nice little hack would be to add a background layer to the circle (it can't be 100% clear or it wouldn't render, so I made it opacity 0.0001 which looks clear) and then add another tapGesture onto that layer. The new gesture will take priority in the background area and we can just leave it without an action so nothing happens.
Circle()
.fill(Color.red)
.frame(width: 100, height: 100, alignment: .center)
.background(
Color.black.opacity(0.0001).onTapGesture { }
)
.onTapGesture {
print("tap")
}

How to expand frame by dx,dy in SwiftUI

For some reason the Text() view that I create sometimes doesn't fit the string correctly in its frame and instead draws the ellipsis -- while I am not explicitly defining the frame of the view. So I would like to "expand" the frame by a dx/dy amount in the x/y directions -- is there a view modifier that I can use for that? ex. Text(" Jx3 ").expand(dx: +4, dy: 0) Or maybe I'm not supposed to use extra space in the string, but then the background shape is too narrow.
viewForPoints(displayHistory[index].displayPoints)
.overlay(
Text(" \(displayHistory[index].abbreviation) ")
.foregroundColor(.gray)
.font(FONTO)
.offset(x: +8, y: +14)
.background(
Capsule()
.fill(Color.white)
.offset(x: +8, y: +14)
)
)
Try to use fixed size, as
.overlay(
Text(" \(displayHistory[index].abbreviation) ")
.fixedSize()
)
Tested & works with some replicated code with Xcode 11.2 / iOS 13.2
Note: overlay uses same frame as its owner, so probably it worth considering ZStack for your case instead.

SwiftUI: Images in a List are no longer center after adding a .frame maxHeight

I'm displaying a List of images, which works fine. They are all centered in the list, but when I add a maxHeight the images move to leading instead of center, so I set the alignment to .center, but that doesn't seem to do anything.
NavigationView {
List(words, id: \.self) { word in
NavigationLink(destination: testView(word: word)) {
Image(word.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame( maxHeight: 200, alignment: .center )
}
}
}
Has anyone else had this problem and know how to solve it?
Thank you
This is the feedback I received from Apple:
Engineering has provided the following information regarding this
issue:
You need the frame to have an infinite width. This is a layout bug.
Please try using a frame with infinite width.
Setting the maxWidth to .infinity did provide a work around for the bug.
For example:
.frame(maxWidth: .infinity, maxHeight: 250)