ScrollView + LazyStacks stutter - swiftui

I've had many similar issues to this when using ScrollViewand LazyVStack/ LazyHStack where the content of the lazy stack will stutter upon bouncing on the edge of the ScrollView.
First I thought it might be due to using complex Views on the lazy stack that would cause layout issues in SwiftUI but I've manage to come up with a MWE that uses a very simple view hierarchy and the issue is still there.
This causes a stutter when scrolling fast to the left and upon bouncing on the leading edge of the ScrollView:
ScrollView(.horizontal) {
LazyHStack {
Color.red.frame(width: 450)
Color.green.frame(width: 250)
Color.blue.frame(width: 250)
}
}
.frame(width: 350)
Decreasing the width of the first view makes the stutter go away
ScrollView(.horizontal) {
LazyHStack {
Color.red.frame(width: 400) //<- No stutter
Color.green.frame(width: 250)
Color.blue.frame(width: 250)
}
}
.frame(width: 350)
For this MWE the stutter only seems to happen on the device (maybe because I can't scroll fast enough in the simulator). However, I've had the same problem happen in the simulator with more complex views.
Any ideas if this is a bug in SwiftUI?
Tested on an iPhones Xs Max with Xcode 13 beta 1 and iOS 15.

Related

VStack is increasing the width based on String

Example
I have tried playing with the padding but no success. Since the string is vertical when you use a larger string the vstack gets wider
var body: some View {
VStack(spacing: 0) {
Spacer()
Text(title.uppercased())
.font(.footnote)
.foregroundColor(.white)
.rotationEffect(Angle(degrees: rotateClockwise ? 90 : -90))
Spacer()
}//: VSTACK
.background(Color.gray.cornerRadius(12))
.frame(width: 85)
}
It's a little tricky to answer with certainty, as there is clearly a more complex layout around this element. So this answer may work or I may need to edit it as you give me a bit more info.
But my suggestion is this: because rotation is not part of SwiftUI's layout calculation, I would suggest using an HStack to wrap just the Text() elements and then rotating that entire stack. This of course then will require you to be more hands-on with the way you use the HStack in your overall layout, but will allow you to get regular stack layout dynamics for your Text elements. To get more control you could consider using the new layout protocol. These are two good tutorials.

AsyncImage is Rotating Portrait Images

I am downloading a JPG image from a remote server. If I use AsyncImage, portrait images taken from the phone's library are rotated 90 degrees. Landscape images taken from the phone's photo library are fine. Also, portrait and landscape photos taken from the phone's camera are fine. Looking at the image on a computer using the URL renders in the correct orientation. If, instead of using AsyncImage to display the image, I instead download it as Data and convert it to a UJIImage, it works fine. So this is only an issue when using AsyncImage. How can I prevent portrait images from rotating 90 degrees while still using AsyncImage?
VStack {
AsyncImage(url: imageURL, transaction: Transaction(animation: .spring())) { phase in
switch phase {
case .empty:
Color.purple.opacity(0.1)
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.transition(.scale)
case .failure(_):
Image(systemName: "exclamationmark.icloud")
.resizable()
.scaledToFit()
#unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
.frame(width: 200, height: 250)
.cornerRadius(10)
}
Update: Although this doesn't specifically answer the question on AsyncImage I was able to get around this issue by performing a transform on the image before it is sent to the server. I know there are issues of orientation when sending as PNG; however, I was sending using jpgData from a UIImage. However, I applied this transform anyway before sending and it worked when receiving via AsyncImage. As a UIImage it would have been fine so some internal process within AsyncImage is handling it differently and thus this additional transform that is otherwise not required for UIImage or any other platform, is required in this instance. Perhaps that is simply the answer, but I'll leave it as is given the question is about the behavior of AsyncImage.

ScrollViewReader scrollTo with .center anchor bug?

So I'm try to use ScrollViewReader to programmatically scroll a horizontal scroll view. I thought it would work like scrollToItem with .centeredHorizontally in UIKit, and for the most part it does, but the last few elements in the scroll view are being forcefully scrolled to the center of the screen, despite the fact that the scroll view isn't normally able to scroll that far over (without snapping back after releasing the drag, at least). This ends up creating white space across the trailing half of the screen.
I've seen some other questions about this and it seems like the general opinion is that it's not a bug? On the one hand I suppose we're telling the scroll view to center the item, and it's doing just that -- so, not a bug? On the other hand, that's not how similar functionality worked in UIKit. Also, this behavior is only happening on the trailing end of the scroll view! If it was the intended behavior I would expect that scrolling to the first element in the scroll view using .center anchor would force it into the center of the screen and leave leading white space, but this doesn't happen.
Is there an elegant solution to this? Or do we have to calculate the width of our elements + spacing and figure out based on the screen width whether we should anchor .center or just scroll to the last element with anchor .trailing in order to replicate the UIKit behavior?
I found a package (Amzd/ScrollViewProxy) that was made before ScrollViewReader was released that functions much the same as ScrollViewReader, but also seems to not have the bug (if it is a bug) detailed in the question.
Usage examples can be seen on the repository page, but here's a quick minimal example.
ScrollView(.horizontal) { scrollProxy in
ForEach(sections) { section in
Text(section.text)
.scrollId(section.id)
}
.onChange(of: index) {
scrollProxy.scrollTo(
sections[index].id,
alignment: .center
)
}
}
The package adds a convenience init to ScrollView to give access to the scrollProxy.
I can confirm this behavior and think it should be considered a bug. Especially since the scroll view will "jump" into position on the first touch event.
As of iOS 15.4 Beta 1 this is fixed for me. Maybe give it another try.

Images imported from ImagePicker being stretched

I've encountered what I think is a bug, and I'm wondering if anyone else has encountered this, and/or has a work around for it.
I have images imported from the device's camera via the UIImagePickerController representable. Having imported this image, I then save it to the documents directory, and then display it using the following code:
Image(uiImage: image)
.resizable()
.frame(height: 300)
.scaledToFill()
This shouldn't cause the image to distort, as scaled to fill should simply enlarge the image until it fits the frame without distorting it. However, i'm getting a fair amount of horizontal stretching in the final image:
Has anyone encountered this problem? I don't think i'm missing anything obvious, as when I use it for images not taken with the camera then the code performs fine.
Try changing the order of the modifiers as below,
Image(uiImage: UIImage(named: "08-512")!)
.resizable()
.scaledToFill()
.frame(width: 50, height: 20)
//.clipped()
}

Circle not centered - expected something else

i have this code. The circle is not centered. I assume because of the edgesIgnoring...and yes, if i comment it out, it is centered. But if i comment out the "scaledToFill" it is centered too, although the edgesIgnoring is in...is this a bug or am i misunderstanding something? I tested on an iPhoneX Preview...
struct ContentView: View {
var body: some View {
Circle()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
}
}
I try to propose solution (one might say it is workaround - does not matter), for some cases it might be acceptable, because both centred properly, so worth posting:
Note: order of modifiers important!
1) approach 1
Ellipse()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
2) approach 2
Circle()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
Short answer, yes, it's a bug. The circle's size is being calculated including the safe area, but the circle's horizontal offset is being calculated to center it assuming the size it would have not including the safe area. Both circles have their left edge at approximately -204 pixels on an iPhone 11, which leaves the larger one uncentered.