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
Related
Goal
I often have the case where I want to scale an image so that it fills its container proportionally without modifying its frame. An example is seen in the first screenshot below. The container view has a fixed frame as indicated by the red border. The image itself has a different aspect ratio. It's scaled to fill the entire container, but it still has the same frame as the container and thus does not affect the container's layout (size).
My Solution
I used the following code to accomplish this:
struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
}
}
struct ImageContainerView: View {
var body: some View {
GeometryReader { geometry in
Image("image")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
}
}
Problem
As you can see, I used a geometry reader to manually set the image's frame to match the parent view's frame. This feels a bit superfluous as the image already receives this information implicitly as the proposed size from its parent view. However, if I don't use the geometry reader this way, the image is not clipped and its frame matches the full scaled image, not the parent view's frame. This is shown in the second screenshot below.
Question
Is there a (more "native") way in SwiftUI to achieve the desired behavior without using a geometry reader?
Here is a way to do it without using GeometryReader. By making the image an .overlay() of another view, that view can handle the clipping with the .clipped() modifier:
struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
}
}
struct ImageContainerView: View {
var body: some View {
Color.clear
.overlay (
Image("image")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
)
.clipped()
}
}
Also, pass in the name of the image so that ImageContainerView and be reused with other images.
Just move .clipped() from the child view to the parent. That is where you want the image clipped:
struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
.clipped() // Here
}
}
struct ImageContainerView: View {
var body: some View {
Image("island")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
}
}
How can I add a circular view similar to the attachment below. In the attachment, the check is the image icon and I want to add the green background color in circular shape. I have a solution in Swift but, couldn't implement the same in swiftUI.
Related posts to my question: Add a border with cornerRadius to an Image in SwiftUI Xcode beta 5. But, this doesn't solve my issue.
Swift code to this implemention:
var imageView = UIImageView()
override init(theme: Theme) {
super.init(theme: theme)
imageView.clipsToBounds = true
setLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
let cornerRadius = frame.height / 2
imageView.setCornerRadius(cornerRadius)
setCornerRadius(cornerRadius)
}
You could create this image like...
Image(systemName: "checkmark")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
Or alternatively...
Image(systemName: "checkmark.circle.fill")
.resizable()
.frame(width: 40, height: 40) // put your sizes here
.foregroundColor(.green)
This is not the simplest thing to come up with. Use this struct as a separate view. It will return the image properly sized on the circle.
struct ImageOnCircle: View {
let icon: String
let radius: CGFloat
let circleColor: Color
let imageColor: Color // Remove this for an image in your assets folder.
var squareSide: CGFloat {
2.0.squareRoot() * radius
}
var body: some View {
ZStack {
Circle()
.fill(circleColor)
.frame(width: radius * 2, height: radius * 2)
// Use this implementation for an SF Symbol
Image(systemName: icon)
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.frame(width: squareSide, height: squareSide)
.foregroundColor(imageColor)
// Use this implementation for an image in your assets folder.
// Image(icon)
// .resizable()
// .aspectRatio(1.0, contentMode: .fit)
// .frame(width: squareSide, height: squareSide)
}
}
}
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):
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)
}
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)
}
}