SwiftUI LazyVGrid lags with images - image

When I scroll my LazyVGrid, app lags a lot.
LazyVGrid(
columns: [
GridItem(.adaptive(minimum: horizontalSizeClass == .compact ? 160:320, maximum: 320), spacing: 16, alignment: .top)
],spacing: 16
){
ForEach(viewModel.articles){ article in
VStack{
ArticleCardView(article: article, animation: animation, show: $show)
.equatable()
ArticleCardView
VStack{
if !show {
HStack{
Text(String(format: "%.2f €", article.rate1))
.fontWeight(.heavy)
.padding(.vertical, 5)
}
Image(uiImage: readImage(name: "\(article.id)00"))
.resizable()
.scaledToFit()
I made this view Equtable, but still lags. Scroll is not fluid.
Any idea to optimize scroll.

One thing to check is the use of an .adaptive GridItem. For an experiment try .flexible - and see if it is faster.
I didn't have the scrolling lag, but noticed that SwiftUI created tiles for all my records when using .adaptive, but acted lazily for .fixed and .flexible

Related

SwiftUI: How to display one image at a time?

I have an array of images which I loop through, and display in a scrollView.
This is want I want to achieve:
I would like there to only be one visible image at a time. Like TikTok and instagram.
I would like the image to take up as much of the screen as possible, while still keeping the aspectRatio.
Center image
I don’t mind using .edgesIgnoringSafeArea([.top, .leading, .trailing])
This is the code I have so far. As you can see there is two images visible at a time. And even when there is only one image in the array, it isn’t centered, but clings to the top of the screen.
Any ideas on how to solve this?
ScrollView(.vertical) {
ForEach(allRetrievedMedia, id: \.self) { item in
switch(item) {
case .image(let img):
Image(uiImage: img)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(alignment: .center)
}
}
}
Embed the image in a container and add .frame(width: screenSize.width, height: screenSize.height, alignment: .center) to that container. Then add .scaledToFill() method to the image itself. This should do the trick, here's a snippet to help...
struct ContentView: View {
let screenSize: CGRect = UIScreen.main.bounds
var body: some View {
ScrollView(.vertical) {
ForEach(allRetrievedMedia, id: \.self) { item in switch(item) {
case .image(let img):
VStack {
Image(uiImage: img)
.resizable()
.scaledToFill()
}.frame(width: screenSize.width, height: screenSize.height, alignment: .center)
}
}
}
}
Haven't tried that myself, but should work like a charm. Hope it helps

SwiftUI Firestore LazyVGrid Navigation Slows with more elements

I have a tab on my fitness app that shows all the fitness trainers on my app, pulled from firestore. I have noticed that when the grid is showing more than 4-5 trainers then when I tap on a trainer (navigation link to their profile) the transition is pretty laggy and slow, and the same thing happens when I press the back button on the trainer profile to return to the grid of all trainers.
Here is my code for the tab showing all trainers:
ScrollView (.vertical, showsIndicators: false, content: {
VStack (alignment: .leading, spacing: 15){
//PAGE TITLE
HStack {
Text("Explore creators")
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.black)
Spacer(minLength: 0)
}
.padding(.horizontal)
LazyVGrid(columns: items, alignment: .leading, spacing: 12, content: {
//FOR EACH LOOP TO SHOW ALL TRAINERS
ForEach(topTrainers) { trainer in
NavigationLink(
destination: TrainerView(trainer: trainer, user: user),
label: {
ExploreGridCell(trainer: trainer)
})
//END OF FOR EACH LOOP
}
//END OF LAZYVGRID
})
.padding(.top, 6)
.padding(.horizontal)
}
.padding(.bottom)
.padding(.top)
})
.background(Color("fiticBg").ignoresSafeArea())
.navigationBarTitle("")
.navigationBarHidden(true)
.onTapGesture {
self.hideKeyboard()
}
Here is my code for what a trainer cell in the lazyVGrid is:
ZStack {
KFImage(URL(string: trainer.trainerImageUrl))
.loadImmediately()
.resizable()
.scaledToFill()
.frame(width: width, height: 175)
.clipped()
Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(gradient: Gradient(colors: [.clear, .black]), startPoint: .center, endPoint: .bottom))
VStack (alignment: .leading, spacing: 5){
//TRAINER CERTIFICATION BADGE
if trainer.trainerCertified {
HStack {
ZStack {
Color(.white)
.frame(width: 15, height: 15)
Image(systemName: "checkmark.seal.fill")
.font(.title2)
.foregroundColor(Color("fiticGreen"))
}
.clipShape(Circle())
Spacer(minLength: 0)
}
} else {
//SHOW NOTHING HERE IF TRAINER IS NOT CERTIFIED
}
Spacer()
//CONTENT TITLE WITH SPACER FOR SECOND LINE OVERLAP
HStack {
Text(trainer.trainerName)
.font(.system(size: 16, weight: .bold))
.foregroundColor(.white)
Spacer(minLength: 0)
}
}
.padding(.horizontal, 10)
//PUSHES TRAINER NAME UP A BIT
.padding(.bottom)
//PUSHES CERTIFICATION BADGE DOWN A BIT
.padding(.top, 5)
}
.cornerRadius(8)
.shadow(color: Color.black.opacity(0.25), radius: 5, x: 0, y: 5)
From other stack overflow questions I have seen things about using a List instead of scrollview, but I have tried and it just does not work with how my view needs to be set up.
Is there anything in my code that would appear to be causing such poor performance and slow transitions for the navigation links?
So from other suggestions it seemed part of the problem was loading the images from firestore since they were such large documents. So I compressed the images and it slightly helped.
However, the thing that helped the most was removing the shadow from trainer cell. That seemed to be causing the most lag.
Nothing in your code seems to cause that lag, except, perhaps, the time to retrieve the Image for the trainer. Anything pulled from the interweb is subject to source of truth delays. One trouble shoot for that would be to pull a dozen or so images locally sourced just to determine if that lag goes away when pulling them from a local source. If so, then you need to look to FireStore

SwiftUI RectangularFullView - Aligning nested Stacks

I'm building a personal apple watch app, and I've just wrapped my head around complications.
I've managed to get mine to display with the proper data, but the View is not aligning on the screen as I would like it.
The stacks are aligning in the center with dynamic widths. The text is also centre aligned.
I tried editing the text with .multilineTextAlignment but that didn't change anything.
I have mocked up the expected alignment below:
The red box is a HStack with an image that is always 60x56 pixels.
I want this to be left aligned, which seems to be working so far.
I also want it to be centered vertically in the view, but I'm not sure if it is even possible.
The pink box is a VStack of text.
This text should be left aligned, but I can't get that to work.
The blue box is the encompassing HStack
Here is my code for this complication (I've just realized I've given the template code with images of a real example, but it should be understandable):
struct ComplicationViewTemplate: View {
var body: some View {
HStack {
HStack {
Image("0i")
.aspectRatio(contentMode: .fit)
}.frame(width: 60, height: 56, alignment: .leading)
VStack {
Text("Egg")
.font(.body)
.bold()
Text("0 steps")
.font(.caption)
Text("Level 01")
.font(.caption)
}
}
}
}
If it helps, since this app is only for me, it's only ever going to be on a Series 5 44mm watch, so items wouldn't have to be dynamic for other watch sizes.
Summary of questions:
Is it possible to vertically centre a HStack inside another HStack
Is it possible to left-align text inside of a VStack
Thanks for any help!
I've managed to get this to work:
struct ComplicationViewTemplate: View {
var body: some View {
HStack {
HStack {
Image("14gi")
.interpolation(.none)
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 56, alignment: .leading)
}.frame(width: 60, height: 56, alignment: .leading)
Spacer(minLength: 10)
VStack(alignment: .leading) {
Text("Pikachu").bold().font(.body).alignmentGuide(.leading, computeValue: { d in d[.leading]
})
Text("000000 steps").font(.caption).alignmentGuide(.leading, computeValue: { d in d[.leading]
})
Text("Level 00").font(.caption).alignmentGuide(.leading, computeValue: { d in d[.leading]
})
}.frame(width: 110, alignment: .leading)
}
}
}
Here's what the result looks like (I've also updated the template slightly):

SwiftUI pin Image to top right of screen and resize based on device size

I just learning Apple's SwiftUI and it is a bit frustrating even doing the basics. I am trying to get my image to the top right of my screen but the default has it showing up in the center of my screen. Anyone know how I can accomplish this. In addition, I tried to get the image to resize based on the screen size but the old self.frame.width that I used to use in regular Swift doesn't work in Swift UI. Sorry, but this new way of writing code in SwiftUI is very odd to me.
var body: some View {
ZStack {
//Define a screen color
LinearGradient (gradient: Gradient(colors:[Color(ColorsSaved.gitLabDark),Color(ColorsSaved.gitLabLight)]),startPoint: .leading,endPoint: .trailing)
//Extend the screen to all edges
.edgesIgnoringSafeArea(.all)
VStack(alignment: .trailing) {
Image("blobVectorDark")
.resizable()
.edgesIgnoringSafeArea(.top)
.frame(width: 60, height: 60)
// .offset(x: , y: 20)
}
}
}
}
You need to add a frame for the outer container, here is the VStack.
Then assign the alignment for this container.
The width and height in the frame of VStack needs to use geometryProxy.
GeometryReader{ (proxy : GeometryProxy) in // New Code
VStack(alignment: .trailing) {
Image("blobVectorDark")
.resizable()
.edgesIgnoringSafeArea(.top)
.frame(width: 60, height: 60)
// .offset(x: , y: 20)
}
.frame(width: proxy.size.width, height:proxy.size.height , alignment: .topLeading) // New Code
}
I'm learning too so this might not be the ideal way to do it, but I did manage to get the image to pin to the top right of the screen.
You can get the screen width from GeometryReader. The alignment was driving me nuts for a while, until I realized I could give a frame to a Spacer to take up the other side of an HStack, and then it worked.
var body: some View {
GeometryReader { geometry in
VStack {
HStack {
Spacer()
.frame(width: geometry.size.width / 2)
Image("blobVectorDark")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
}
Spacer()
}
}
.edgesIgnoringSafeArea(.all)
}

SwiftUI add image to left side of text field

I'm trying to get an SF Symbol on the left side of a text field in SwiftUI, but the methods used before SwiftUI don't really work. I thought this code would work, but it didn't. Also if anyone could help, how would I change the font and color of the text in the text field?
Here's an image of what I'm trying to accomplish: https://i.stack.imgur.com/Dud0M.png
TextField(textFieldDescription, text:
.constant(""))
.padding(.all)
.background(textFieldBackground)
.cornerRadius(10)
.leftViewMode = Image(systemName: "lock").always
.leftViewMode = .always
Here's one implementation. Note that there is a bug, where if the initial text of the textfield is empty, the color and font setting will be ignored:
HStack {
Image(systemName: "lock")
TextField("", text: .constant("typed text")).foregroundColor(Color.red).font(Font.custom("Papyrus", size: 16))
}
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 2).foregroundColor(Color.black))
SwiftUI, XCode 11.6
This code worked for me.
Note:-
The yellow border outside the view is background view color which will not be there in your case. Outer-most HStack holds another HStack(inner HStack) of items image and TextField. To inner HStack
Spacer(minLength: 20) is added move away from the edge of the screen.
HStack {
Spacer(minLength: 20)
HStack (alignment: .center,
spacing: 10) {
Image("userName")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.foregroundColor(.black)
.frame(minWidth: 0, maxWidth: 30)
.frame(minHeight: 0, maxHeight: 33)
TextField ("User Name", text: $userName)
} // HSTack
.padding([.top,.bottom], 2)
.padding(.leading, 5)
.background(Color.white, alignment: .center)
.cornerRadius(5)
Spacer(minLength: 20)
}
Here is my implementation with ZStack:
ZStack(alignment: .trailing) {
TextField("", text: $text)
.font(Font.system(size: 21))
Button(action: {
//Some button action
}) {
Image("iconName").resizable()
}
.frame(width: 35, height: 35)
.padding(.trailing, 10)
}
If you can, I might recommend encapsulating your TextField and other inputs within SwuiftUI's Form container around your TextField inputs. This will provide you with some "freebie" styling that can make what you're looking for a bit easier. For example, in iOS and iPadOS, the Form wrapper renders each element as an item in a List with the .grouped style. (It's a bit different for macOS.)
Here's what I used to achieve the style you're looking for. One note: SF Symbols aren't all uniform in their width, so you'd want to explicitly set the frame dimensions and padding to get everything lined up perfectly.
Form {
HStack{
Image(systemName:"envelope")
// play with the frame and padding here
TextField("Email Address", text: $email)
}
HStack{
Image(systemName:"lock")
SecureField("Password", text: $password)
}
}

Resources