How to align bottom edge to bottom trailing of above view SwiftUI - image

I'm currently trying to align the person image bottom trailing to the bottom edge of the image in a SwiftUI View.
Code:-
struct AddSecondImageOnSameImage: View {
#State var image = Image(systemName: "person.circle.fill")
var body: some View {
VStack{
self.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
.padding(1)
.overlay(Circle()
.frame(width: 20, height:20)
.foregroundColor(Color.init(UIColor.init(displayP3Red: 255/255, green: 92/255, blue: 210/255, alpha: 1.0))),alignment: .bottomTrailing)
}
}
}
Output:-
]
Want to achieve:-
Can someone please explain to me how to align the person image bottom trailing to the bottom edge of the image in a SwiftUI View. I've tried to implement by above but no results yet.
Any help would be greatly appreciated.
Thanks in advance.

Use ZStack instead of .overlay()
The ZStack assigns each successive child view a higher z-axis value than the one before it, meaning later children appear “on top” of earlier ones.
https://developer.apple.com/documentation/swiftui/zstack
struct AddSecondImageOnSameImage: View {
#State var image = Image(systemName: "person.circle.fill")
var body: some View {
ZStack(alignment: .bottomTrailing) {
self.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
.padding(1)
Circle()
.frame(width: 20, height:20)
.foregroundColor(Color.init(UIColor.init(displayP3Red: 255/255, green: 92/255, blue: 210/255, alpha: 1.0)))
.offset(x: -5, y: -5)
}
}
}
You may need to adjust the offset.

By using a Z-Stack and defining the frame, you can align the views inside by manipulating their frame using .infinity and setting the alignment to the desired outcome. For example in your scenario:
import SwiftUI
struct SampleView: View {
#State var image = Image(systemName: "person.circle.fill")
var body: some View {
ZStack {
self.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100, alignment: .center)
.clipShape(Circle())
.padding(1)
Circle()
.frame(width: 20, height:20)
.foregroundColor(.green)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
}
.frame(width: 100, height: 100)
}
}
There are several ways to implement it but setting the frame and the alignment is required. Avoid using offset constants since this would not work the same everywhere, for example, in responsive resizing.
SampleView

Related

How to use swiftUI matched geometry to present a detail card view on top of elements (instead of increasing frame height)

I am attempting my first steps using matchedgeometry and have followed some tutorials to get a simple view to expand when selected.I have two files, a card view and a content view. I would like to place all of my card views in a horizontal scrollview.
import SwiftUI
struct smallCard: View {
#State private var flag: Bool = true
#Namespace var nspace
var body: some View {
if flag {
VStack{
Image("chemex.jpg")
.resizable()
.matchedGeometryEffect(id: "image", in: nspace)
.scaledToFill()
Spacer()
Button("CHEMEX") { withAnimation(.default) { flag.toggle() } }
.matchedGeometryEffect(id: "text", in: nspace)
Spacer()
.frame(height:8)
}
.matchedGeometryEffect(id: "geoeffect1", in: nspace)
.frame(width: 100, height: 100, alignment: .center)
.background(Color.white)
.cornerRadius(20)
.shadow(color: .gray, radius: 9, x: 0, y: 9)
}
if !flag {
VStack{
Image("chemex.jpg")
.resizable()
.matchedGeometryEffect(id: "image", in: nspace)
.scaledToFit()
Spacer()
Button("CHEMEX") { withAnimation(.default) { flag.toggle() } }
.matchedGeometryEffect(id: "text", in: nspace)
Spacer()
.frame(height:20)
}
.matchedGeometryEffect(id: "geoeffect1", in: nspace)
.layoutPriority(1)
.frame(width: 300, height: 600, alignment: .center)
.background(Color.white)
.cornerRadius(20)
.shadow(color: .gray, radius: 9, x: 0, y: 9)
}
}
I the have my main content view:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
HStack{
Text("My App")
.font(.title)
.padding(.leading, 25)
Spacer()
}
ScrollView(.horizontal){
HStack{
smallCard()
.padding(.horizontal)
smallCard()
.padding(.horizontal)
smallCard()
.padding(.horizontal)
}
}
}
.frame(alignment: .center)
}
This currently works for clicking on the small card and the matched geometry animates this to the larger card. The issue I have is that this changes the height of my scrollview and as such this moves everything else such as my title up and out of view.
Effectively what I would like to achieve is like a popover view, so that this detail view is on top of all of the elements in my content view, but whilst using matchedgeometry (or an alternative solution) to animate nicely between the views.
Is this possible? I am at the point where I do not know if there is a solution to this.

How to get round shape images of the same size based on SF Symbols in SwiftUI?

In my application I want to get simple round buttons based on SF Symbols of the same size. However, the same approach results in different image sizes depending on the symbol.
For example, an image with a plus sign is larger than a minus sign.
To solve this problem, I use the ZStack trick in which I put a transparent plus under the minus. But I think this is not the best solution. Are there any better solutions?
HStack{
Image(systemName: "plus")
.padding()
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
Image(systemName: "minus")
.padding()
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
//my solution
ZStack {
Image(systemName: "plus")
.padding()
.opacity(0.0)
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
Image(systemName: "minus")
}
}
"minus" in the center has a smaller size than "plus", "minus" on the right - my solution:
You can use ViewModifier or if are buttons ButtonStyle
ViewModifier
#available(iOS 13.0, *)
struct fillButtonCircle: ViewModifier {
var foregroundColor: Color = .white
var backgroundColor: Color = .green
var dimension: CGFloat = 10
func body(content: Content) -> some View {
content
.foregroundColor(foregroundColor)
.padding(dimension)
.background(backgroundColor)
.clipShape(Circle())
.frame(width: dimension, height: dimension)
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
}
}
ButtonStyle
#available(iOS 13.0, *)
struct CircleScaleButton: ButtonStyle {
var color: Color = .blue
var maxHeight: CGFloat = 35
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: maxHeight, alignment: .center)
.foregroundColor(.black)
.background(RoundedRectangle(cornerRadius: 35/2.0).fill(self.color))
.compositingGroup()
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
.opacity(configuration.isPressed ? 0.8 : 1.0)
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
}
}
Example
struct SwiftUIViewTest: View {
var body: some View {
VStack {
Text("Button")
HStack {
Button(action: {}, label: {
Image(systemName: "plus")
})
.buttonStyle(CircleScaleButton(color: .clear, maxHeight: 45))
Button(action: {}, label: {
Image(systemName: "minus")
})
.buttonStyle(CircleScaleButton(color: .clear, maxHeight: 45))
}
Spacer()
.frame(height: 50)
Text("Image")
HStack {
Image(systemName: "plus")
.modifier(fillButtonCircle(foregroundColor: .black, backgroundColor: .clear, dimension: 40))
Image(systemName: "minus")
.modifier(fillButtonCircle(foregroundColor: .black, backgroundColor: .clear, dimension: 40))
}
}
}
}
use .circle
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Image(systemName: "plus.circle")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
Image(systemName: "minus.circle")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
}
}
}

SwiftUI - Move and rotate an Image when a button is pressed

I have an Image, and I want it to rotate on the y axis, and move towards the bottom of the View when I press a Button. I tried using the .onChange, but I get the error "Result of call to 'animation' is unused", of which I understand the meaning, but I don't understand neither why it comes up nor how can I fix it.
Here's my code:
import SwiftUI
struct ContentView : View {
#State var animateCoin = false
#Environment(\.colorScheme) var colorScheme
var body: some View {
Rectangle()
.foregroundColor(colorScheme == .dark ? .white : .black)
.frame(width: 150, height: 150, alignment: .center)
.offset(y: -60)
.mask(Image("Coin") //That's the name of the Image I have in the assets
.resizable()
.onChange(of: animateCoin, perform: { value in
self.animation(.easeIn) //I put .easeIn just as an example, because the .rotation3DEffect gives me a red warning
}))
Button(action: { animateCoin = true } ) {
ZStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.green)
.frame(width: 100, height: 40, alignment: .center)
.shadow(radius: 10)
Text("Animate")
.foregroundColor(.white)
.shadow(radius: 20)
}
}
}}
The Image is set as a mask so that I can easily control its color depending on the light or dark mode.
Thank to everyone who will help me!
How about doing it like this:
#State var animateCoin = false
#Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
Rectangle()
.foregroundColor(colorScheme == .dark ? .white : .black)
.frame(width: 150, height: 150, alignment: .center)
.offset(y: -60)
.mask(Image(systemName: "car.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.rotation3DEffect(
Angle(degrees: self.animateCoin ? 360 : 0),
axis: (x: 0, y: self.animateCoin ? 360 : 0, z: 0)
)
)
.offset(y: self.animateCoin ? 600 : 0)
.animation(.linear(duration: 1))
ZStack {
Button(action: { self.animateCoin.toggle() } ) {
ZStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.green)
.frame(width: 100, height: 40, alignment: .center)
.shadow(radius: 10)
Text("Animate")
.foregroundColor(.white)
.shadow(radius: 20)
}
}
}
}
}
As you asked:
Rotate on Y axis.
Move the image to the buttom.

Problem with formatting LazyHGrid - SwiftUI

I am trying to create a basic lazyHGrid but after looking at a couple of different tutorials online I still can't manage to build it properly. I am trying to make a grid that is filled with rectangles with symbols and text inside those rectangles. See image below:
If I make this in a HStack all the rectangles are presented correctly, but as soon as I put it into a LazyHGrid I get this problem. Any ideas?
let rows = [
GridItem(.fixed(200)),
GridItem(.fixed(200)),
]
var product: ProductModel
var body: some View {
LazyHGrid(rows: rows, alignment: .center) {
ForEach(0..<product.imageStack01Image.count, id: \.self) { item in
ZStack(alignment: .top){
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
.frame(width: 200, height: 200)
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 5)
VStack(alignment: .leading) {
Image(systemName:product.imageStack01Image[item])
.font(.system(size: 40.0))
.frame(height: 40)
.foregroundColor(.red)
.padding(.top, 40)
.padding(.leading, 10)
Text(product.imageStack01Text[item])
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
.padding(10)
}
}
}
}
}
As far as I understood your try to put image and text into rectangle, so try to use overlay instead of ZStack
ForEach(0..<product.imageStack01Image.count, id: \.self) { item in
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
.frame(width: 200, height: 200)
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 5)
.overlay(
VStack(alignment: .leading) {
Image(systemName:product.imageStack01Image[item])
.font(.system(size: 40.0))
.frame(height: 40)
.foregroundColor(.red)
.padding(.top, 40)
.padding(.leading, 10)
Text(product.imageStack01Text[item])
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
.padding(10)
})
}
}
It might have to do with the hardcoded frame in your square. Try using aspectRatio if you just want a square or frame each layer of the ZStack or frame the ZStack depending on what you want to achieve.
import SwiftUI
struct LazyGridSample: View {
///Sample Products to reproduce
#State var products = ["0xxxxxxxxxx0xxxxxxxxxx","1xxxxxxxxxx0xxxxxxxxxx","2xxxxxxxxxx0xxxxxxxxxx","3xxxxxxxxxx0xxxxxxxxxx","4xxxxxxxxxx0xxxxxxxxxx","5xxxxxxxxxx0xxxxxxxxxx","6xxxxxxxxxx0xxxxxxxxxx","7xxxxxxxxxx0xxxxxxxxxx","8xxxxxxxxxx0xxxxxxxxxx","9xxxxxxxxxx0xxxxxxxxxx"]
let rows = [
GridItem(.fixed(200)),
GridItem(.fixed(200)),
]
let squareSize: CGSize = CGSize(width: 200, height: 200)
var body: some View {
//ScrollView(.horizontal){
LazyHGrid(rows: rows, alignment: .center) {
//HStack{
ForEach(0..<products.count, id: \.self) { idx in
ZStack(alignment: .top){
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
//.frame(width: squareSize.width, height: squareSize.height)
//.aspectRatio(1, contentMode: .fill)
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 5)
VStack(alignment: .leading) {
//Removed Image
Text(products[idx].description)
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
.padding(10)
}//.frame(width: squareSize.width, height: squareSize.height, alignment: .center)
//.aspectRatio(1, contentMode: .fill)
}//Adjusts the the entire ZStack
//.frame(width: squareSize.width, height: squareSize.height, alignment: .center)
.aspectRatio(1, contentMode: .fill)
}
}
//}//ScrollView
}
}
struct LazyGridSample_Previews: PreviewProvider {
static var previews: some View {
LazyGridSample()
}
}

Clip clickable parts of image as NavigationLink (SwiftUI)

When implementing an image in a NavigationLink closure and clipping that image, the unclipped image is clickable. Because of the clipping, the images are overlapping each other (see attached screenshots).
The first screenshot shows the original size. When clipped (second screenshot) clicking on the red hatched area (shown in the first screenshot), the second NavigationLink is triggered, not the first one.
The following code produces the problem:
var body: some View {
NavigationView{
ScrollView{
VStack (spacing: 20) {
NavigationLink(destination: ImageGalleryView1()) {
Image(uiImage: downsample(imageAt: URL(string: "imageURL")!, to: CGSize(width: 500, height: 500), scale: 1))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipped()
}
NavigationLink(destination: ImageGalleryView2()) {
Image(uiImage: downsample(imageAt: URL(string: "imageURL")!, to: CGSize(width: 500, height: 500), scale: 1))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipped()
}
NavigationLink(destination: ImageGalleryView3()) {
Image(uiImage: downsample(imageAt: URL(string: "imageURL")!, to: CGSize(width: 500, height: 500), scale: 1))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipped()
}
}
}
}
I tried to clip the Image, tried to clip the NavigationLink, played around with the .frame()-properties. But with no success.
My aim is to create a VStack with three images where each of them is a NavigationLink. The clipped parts shouldn't be clickable. I want to avoid buttons or shapes in this case, if possible.
Just add .contentShape(Rectangle()) directly before .clipped(). That solved this issue for me.
I don't know your exact implemetation of downsample(imageAt:), but the implementation below gets rid of the overlapping of images:
NavigationLink(destination: ImageGalleryView1()) {
Image("sample-image")
.resizable(resizingMode: .tile)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: 200,
alignment: .center)
}

Resources