I have a View in my app that shows an Image as the background (via Binding) and then it has a button to download the image and another to favorite the image.
The problem is that when I use the .ignoreSafeArea on a whole ZStack, the tap area on the button shifts to below the button body. I have the images scaled to fit and clipped and I tried moving the buttons down with padding, and I have tried seeing if there was an alternative to making the whole ZStack ignore the safe area with no luck.
I should also mention that I have put the image as the background with the .background property, I tried just putting it at the top of a ZStack but that would also affect the buttons positions.
I should also mention I am relatively new to Swift
struct photoDetail: View {
let image: realPhoto
#State var textControl: Bool
init(image: realPhoto) {
self.image = image
self.textControl = image.favorited
}
var body: some View {
ZStack {
VStack(alignment: .leading) {
Spacer()
HStack {
Text("created by: \(image.username)")
.padding(5)
.foregroundColor(.white)
.background(Color(.gray).opacity(0.8))
.padding(.leading, 20)
.font(.system(size:20, weight: .semibold))
.padding(.bottom, 50)
.cornerRadius(4)
Spacer()
}.padding(.bottom, 60)
}
VStack {
HStack {
Spacer()
Button {
let imageSaver = ImageSaver()
imageSaver.writeToPhotoAlbum(image: UIImage(data: image.image)!)
} label: {
Image(systemName: "square.and.arrow.down")
.resizable()
.scaledToFit()
.frame(width: 20)
.padding(8)
}
Button {
favoriteToggle()
} label: {
Image(systemName: textControl == false || image.favorited == false ? "heart": "heart.fill")
.resizable()
.scaledToFit()
.frame(width: 25)
.padding(8)
}.padding(.trailing, 30)
}.padding(.top, 100)
Spacer()
}
}.onAppear() {
self.textControl = image.favorited
}.background(
Image(uiImage: UIImage(data: image.image)!)
.resizable()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.scaledToFill()
.clipped()
).ignoresSafeArea()
}
}
Try to add .contentShape(Rectangle()) to your buttons. It should limit tappable area to your button view
I'm trying to get an image of a logo to be a button. I'm new to Swift/SwiftUI and the resources I've found so far seem to be outdated information. I have the image loaded in to see that I can get the image in there, and I've created a button in the location I want it, I just want to combine the two. Code is below.
struct ContentView: View {
var body: some View {
VStack {
Image("logo1024")
.resizable()
.padding()
.frame(width: 120, height: 120)
Group{
Button(action: {
print("tapped!")
}, label: {
Text("+")
.foregroundColor(.white)
.frame(width: 40, height: 40)
.background(Color.green)
.cornerRadius(15)
.padding()
})
}.frame(maxHeight: .infinity, alignment: .bottom)
}
}
}
Just place image instead of Text into button (all other modifiers on your needs), like
Group{
Button(action: {
print("tapped!")
}, label: {
Image("logo1024")
.resizable()
.padding()
.frame(width: 120, height: 120)
.foregroundColor(.white)
.frame(width: 40, height: 40)
.background(Color.green)
.cornerRadius(15)
.padding()
})
}.frame(maxHeight: .infinity, alignment: .bottom)
A whiteness is seen in the area drawn with the red line. If I change the background color of the most inclusive Vstack, that white area changes.
Deleting spacer() lines doesn't work.
Why is there a gap even though there is no space in between?
struct TabbarView: View {
var body: some View {
VStack{
Spacer()
ZStack{
Color.orange.opacity(0.5)
VStack(spacing: 0){
Text("Home")
.padding()
}
}
Spacer()
HStack{
VStack{
Image(systemName: "homekit")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: UIScreen.main.bounds.size.width / 15, height: UIScreen.main.bounds.size.height / 25)
}
}
.frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 13)
.background(Color.purple)
}
.ignoresSafeArea()
// .background(Color.purple.shadow(radius: 2))
}
}
enter image description here
you can add for VStack:
VStack {}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
updated:
struct ContentView: View {
var body: some View {
ZStack {
Color.orange.opacity(0.5)
VStack {
Spacer()
VStack(spacing: 0){
Text("Home")
.padding()
}
Spacer()
HStack {
VStack {
Image(systemName: "homekit")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.frame(width: UIScreen.main.bounds.size.width / 15, height: UIScreen.main.bounds.size.height / 25)
}
}
.frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 13)
.background(Color.purple)
}
}
.ignoresSafeArea()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
you used nesting incorrectly and there is a native TabView for tabs
result:
First of all, before you link another stack overflow post using something like modal, I already tried but couldn't implement it because of the foreach. my contentview code looks like this:
NavigationView{
ScrollView{
Group{
VStack{
//just the current stuff
Text("Current Forecast")
Text("condition: \(f.responses.current.condition?.text ?? "Loading...")")
Text("temp: \(Int(Double.rounded(f.responses.current.temp_f)())) degrees fahrenheit")
}
.frame(width: UIScreen.main.bounds.width - 30, height: 120, alignment: .center)
.background(Color.gray.opacity(0.8))
.cornerRadius(20)
.foregroundColor(.black)
.font(.system(size: 18).bold())
.opacity(0.4)
//iterates through the hours of the day
ScrollView(.horizontal){
LazyHStack {
ForEach(0..<f.responses.forecast.forecastday[0].hour.count){index in
NavigationLink(destination: detailedView(info: f.responses.forecast.forecastday[0].hour[index])
.navigationBarBackButtonHidden(true)
.edgesIgnoringSafeArea(.all),
label: {listView(h: f.responses.forecast.forecastday[0].hour[index]).frame(width: 90, height: 100, alignment: .center).background(Color.gray).opacity(0.4).cornerRadius(20)})
}
}
}.navigationBarHidden(true)
}
}.navigationBarHidden(true).background(
Group{
//checks if it is night
if (timeToInt(f.responses.location.localtime) < 19 && timeToInt(f.responses.location.localtime) > 5){
Image("\(f.responses.current.condition?.code ?? 1000)")
.resizable()
.aspectRatio(contentMode: .fill)
//if it is night, uses a different image
}else{
Image("night")
.resizable()
.aspectRatio(contentMode: .fill)
}
}
)
} .background(
Group{
//checks if it is night
if (timeToInt(f.responses.location.localtime) < 19 && timeToInt(f.responses.location.localtime) > 5){
Image("\(f.responses.current.condition?.code ?? 1000)")
.resizable()
.aspectRatio(contentMode: .fill)
//if it is night, uses a different image
}else{
Image("night")
.resizable()
.aspectRatio(contentMode: .fill)
}
}
)
Meanwhile my "detailedview" code looks like this:
import SwiftUI
struct detailedView: View {
var info : Hour
#Environment(\.presentationMode) var presentationMode
public var body: some View {
let rawTime = info.time
//rounding
let displayTemp_F = Int(Double.rounded(info.temp_f)())
let displayFeelslike_f = Int(Double.rounded(info.feelslike_f)())
let displayWind_mph = Int(Double.rounded(info.wind_mph)())
GeometryReader{geo in
VStack{
Button(action: {
self.dismissSelf()
}) {
Text("Dismiss Me!")
}
Spacer()
Group{
//displaying all the data
Text(info.condition?.text ?? "API error")
Text("Time: \(String(rawTime[rawTime.lastIndex(of: " ")!...]))")
Text("Temperature: \(displayTemp_F) degrees fahrenheit")
Text("Feels like: \(displayFeelslike_f) degrees fahrenheit")
Text("Wind MPH: \(displayWind_mph)")
}
.frame(width: UIScreen.main.bounds.width - 30, height: 50, alignment: .center)
.background(Color.white.opacity(0.5))
.cornerRadius(20)
.foregroundColor(.black)
.font(.system(size: 18).bold())
//all of the . stuff are for making the text look nice
Spacer()
Spacer()
Spacer()
} //making things look nice
.frame(width: geo.size.width, height: geo.size.height, alignment: .top)
.background(
Group{
if (timeToInt(info.time) < 19 && timeToInt(info.time) > 5){
Image("\(info.condition?.code ?? 1000)")
.resizable()
.scaledToFill()
.frame(width: geo.size.width, height: geo.size.height, alignment: .top)
}else{
Image("night")
.resizable()
.scaledToFill()
.frame(width: geo.size.width, height: geo.size.height, alignment: .top)
}
}
)
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height, alignment: .top)
}
private func dismissSelf() {
presentationMode.wrappedValue.dismiss()
}
}
PLEASE DO NOT LINK ANOTHER STACK OVERFLOW POST UNLESS YOU ARE SURE IT IS RELATED! THE LAST COUPLE QUESTIONS I ASKED HAD PEOPLE LINKING UNRELATED POSTS!
sorry for that. I am basically just trying to create a "back" button that doesn't blend in with the background. I also do not want a back button that takes up the entire top bar. Any help would be appreciated!
I also just found out about .toolbar, and I'm not sure if I could use that to put a custom back button up.
I fixed this by simply setting the back button to invisible and putting an image behind it. I added this code:
class Theme {
static func navigationBarColors(background : UIColor?,
titleColor : UIColor? = nil, tintColor : UIColor? = nil ){
let navigationAppearance = UINavigationBarAppearance()
navigationAppearance.configureWithOpaqueBackground()
navigationAppearance.backgroundColor = background ?? .black
navigationAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .black]
navigationAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .black]
UINavigationBar.appearance().standardAppearance = navigationAppearance
UINavigationBar.appearance().compactAppearance = navigationAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationAppearance
UINavigationBar.appearance().tintColor = tintColor ?? titleColor ?? .black
}
}
it allows me to change the back button. I then added this:
init(){
Theme.navigationBarColors(background: .clear, titleColor: .clear)
}
This got added to contentview struct, and makes the back button invisible. Far from the best way to do it, but it is simple.
How can I center horizontally a View (Image) in an HStack? I want a button to be left aligned and the image to be centered horizontally the view.
Currently I have this structure:
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image("left-arrow")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
}).padding(.leading, 20)
Spacer()
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
Spacer()
}
Which is giving me this:
But I want to achieve this:
You can embed two HStack's in a ZStack and place spacers accordingly for the horizontal spacing. Embed all that in a VStack with a Spacer() to have everything pushed up to the top.
struct ContentView : View {
var buttonSize: Length = 30
var body: some View {
VStack {
ZStack {
HStack {
Button(action: {
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .leading)
}).padding(.leading, CGFloat(20))
Spacer()
}
HStack {
Image(systemName: "star")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .center)
}
}
Spacer()
}
}
}
Note: In the second HStack, the image should automatically be center aligned, but if it isn't, you can place a Spacer() before and after the image.
Edit: Added the VStack and Spacer() to move everything to the top like the OP wanted.
Edit 2: Removed padding on image because it caused the image to be slightly offset from the center. Since it is in its own HStack and center-aligned, it does not need padding.
Edit 3: Thanks to #Chris Prince in the comments, I decided to make a simple NavigationBar-esque custom view that you can provide left, center, and right arguments to create the effect that the OP desired (where each set of views are aligned independently of each other):
struct CustomNavBar<Left, Center, Right>: View where Left: View, Center: View, Right: View {
let left: () -> Left
let center: () -> Center
let right: () -> Right
init(#ViewBuilder left: #escaping () -> Left, #ViewBuilder center: #escaping () -> Center, #ViewBuilder right: #escaping () -> Right) {
self.left = left
self.center = center
self.right = right
}
var body: some View {
ZStack {
HStack {
left()
Spacer()
}
center()
HStack {
Spacer()
right()
}
}
}
}
Usage:
struct ContentView: View {
let buttonSize: CGFloat = 30
var body: some View {
VStack {
CustomNavBar(left: {
Button(action: {
print("Tapped")
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: self.buttonSize, height: self.buttonSize, alignment: .leading)
}).padding()
}, center: {
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}, right: {
HStack {
Text("Long text here")
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.padding(.trailing)
}.foregroundColor(.red)
})
Spacer()
Text("Normal Content")
Spacer()
}
}
}
What's about saving button size to a property and add a negative padding to the image? And pay attention to an additional spacer after the image.
struct ContentView: View {
var buttonSize: Length = 30
var body: some View {
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: buttonSize, height: buttonSize, alignment: .leading)
})
Spacer()
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.padding(.leading, -buttonSize)
Spacer()
}
Spacer()
}
}
}
The result:
Easiest way for me:
ZStack(){
HStack{
Image("star").resizable().foregroundColor(.white).frame(width: 50, height: 50)
Spacer()
}
Image("star").resizable().font(.title).foregroundColor(.white).frame(width: 50, height: 50)
}
You center the view using position property try this code
Group{ // container View
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}.position(x: UIScreen.main.bounds.width/2)
the right way to center the Title like navigationbar:
HStack {
Spacer()
.overlay {
HStack {
Image(systemName: "star")
Spacer()
}
}
Text("Title")
Spacer()
.overlay {
HStack {
Spacer()
Image(systemName: "star")
}
}
}
You can place the view that you want to center into a VStack and then set the alignment to center. Make sure that you also set the frame(maxWidth: .infinity) or else it will be centering your view in the VStack but the VStack might not take up the entire width of the screen so you might not get the appearance you are trying to achieve.
To make it even easier, write it as a function that extends the View object
extension View {
func centerInParentView() -> some View {
VStack(alignment: .center) {
self
}
.frame(maxWidth: .infinity)
}
}
And then you can just call it as you would a view modifier i.e.
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image("left-arrow")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
}).padding(.leading, 20)
Spacer()
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
Spacer()
}
.centerInParentView()
Works every time for me
I have got an alternative solution. I used a hidden Image as placeholder.
HStack {
Image("left-arrow").padding()
Spacer()
Image("twitter-logo")
Spacer()
// placeholder to keep layout symmetric
Image("left-arrow").padding().hidden()
}
Of course you can replace the Images with Buttons or other Views as you prefer.
Here is what worked for me
HStack {
Image(systemName: "star.fill")
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: "star.fill")
.frame(maxWidth: .infinity, alignment: .center)
Text("")
.frame(maxWidth: .infinity, alignment: .trailing)
}
.foregroundColor(.yellow)
Inspired by SwiftUI - How to align elements in left, center, and right within HStack?
Let me propose a different solution:
https://gist.github.com/buscarini/122516641cd0ee275dd367786ff2a736
It can be used like this:
HStack {
Color.red
.frame(width: 0, height: 50)
.layoutPriority(1)
GlobalHCenteringView {
Text("Hello, world!")
.lineLimit(1)
.background(Color.green)
}
.background(Color.yellow)
Color.red
.frame(width: 180, height: 50)
.layoutPriority(1)
}
}
This will center the child view in the screen if it fits, or leave it as is if it doesn't. It is currently using UIScreen, so it only works on iOS, but you could easily pass the screen or parent width to the constructor of the view, getting it from a GeometryReader or whatever.