SwiftUI Form picker with Image and Text - ios

I have a Setting View in my app that provides an option to select a value from picker with this code:
var body: some View {
NavigationView {
Form {
Section(header: Text("Widget Settings")) {
Picker(selection: $chosenMediumType, label: Text("Medium Widget"), content: {
VStack {
Image(uiImage: UIImage(systemName: "sun.min")!).resizable().frame(width: 20, height: 20, alignment: .center)
Text("Sun")
}.tag(0)
VStack {
Image(uiImage: UIImage(systemName: "sunset")!).resizable().frame(width: 20, height: 20, alignment: .center)
Text("Sunset")
}.tag(1)
VStack {
Image(uiImage: UIImage(systemName: "moon")!).resizable().frame(width: 20, height: 20, alignment: .center)
Text("Moon")
}.tag(2)
})
.onChange(of: chosenMediumType) { print("Selected tag: \($0)") }
}
}
.navigationBarTitle("Settings")
}
}
When I click the picker row it's open the picker page and I can see each row with image and text, but In the settings, it makes the row bigger as the image shown:
It is possible to use text only in the settings page and image+text in the picker view?

view
I just wanted to show you the way you can do it,
Just hide whole picker, it will stay have its without picker inside and overlay HStack, inside stack make a switch case or if or whatever you want
struct ContentView: View {
#State private var chosenMediumType = 0
var body: some View {
NavigationView {
Form {
Section(header: Text("Widget Settings")) {
Picker(selection: $chosenMediumType, label: Text("")
, content: {
VStack {
Image(uiImage: UIImage(systemName: "sun.min")!).resizable().frame(width: 20, height: 20, alignment: .center)
Text("Sun")
}.tag(0)
VStack {
Image(uiImage: UIImage(systemName: "sunset")!).resizable().frame(width: 20, height: 20, alignment: .center)
Text("Sunset")
}.tag(1)
VStack {
Image(uiImage: UIImage(systemName: "moon")!).resizable().frame(width: 20, height: 20, alignment: .center)
Text("Moon")
}.tag(2)
})
.hidden()
.overlay(
HStack(alignment: .center, spacing: nil, content: {
Text("Medium Widget")
Spacer()
switch chosenMediumType {
case 1:
Text("Sunset")
.foregroundColor(.gray)
case 2:
Text("Moon")
.foregroundColor(.gray)
default:
Text("Sun")
.foregroundColor(.gray)
}
})
)
.frame(height: 30)
}
}
.navigationBarTitle("Settings")
}
}
}

Related

SwiftUI Custom Navigation Bar VStack doesn't work

I'm trying to make a custom navigation bar with back button, image, VStack (2 labels) but it didn't work. The whole view will stick to the center and not following the alignment I set. Thank you!
struct WeatherNavigation: View {
var body: some View {
HStack {
WeatherNavigation()
}
.
.
.
}
}
//
struct WeatherNavigation: View {
var body: some View {
Button(action: {
//action
}, label: {
HStack {
Image("Back")
.foregroundColor(.black)
Image("Weather")
.resizable()
.frame(width: 40, height: 40)
}
})
.frame(width: 100, height: 50, alignment: .leading)
VStack {
Text(weather.description)
.font(.appFont(size: 18))
.foregroundColor(Color(uiColor: .black))
Text(weather.location)
.font(.appFont(size: 12))
.foregroundColor(Color(uiColor: .blue))
}
.frame(width: .infinity, height: 50, alignment: .leading)
}
}
First of all, you shouldn't set frame of the whole view like that. For you problem, it can divide into: make a HStack to store all the container view and make a space between those two of them. Because using HStack you don't need to add leading.
Code will be like this
struct WeatherNavigation: View {
var body: some View {
// make a HStack to store all the attribute
HStack(alignment: .top) {
Button(action: {
//action
}, label: {
HStack {
Image("Back")
.foregroundColor(.black)
Image("Weather")
.resizable()
.frame(width: 40, height: 40)
}
})
.frame(width: 100, height: 50)
VStack {
Text("Tokyo")
.foregroundColor(Color(uiColor: .black))
Text("Japan")
.foregroundColor(Color(uiColor: .blue))
}
// make a space at the end
Spacer()
}
}
}
And the usage like this
struct ContentView: View {
var body: some View {
VStack {
HStack {
WeatherNavigation()
}
Spacer()
}
}
}
More over: adding Spacer() means that make space between view. That will solved your problem if you want to keep like your way.

Swift ui stacks causing gap at the top of the screen when frame should be first

In Swift ui I have a tabview and am going to my profile test page but in the simulator it displaying in the middle of the page.
var body: some View {
VStack() {
Text("Welcome")
.font(.largeTitle)
.foregroundColor(.black)
ZStack {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(.red)
VStack {
Image("murry")
.clipShape(Circle())
.shadow(radius: 10)
.overlay(Circle()
.stroke(Color.red, lineWidth: 5))
Text("Murry GoldBerg")
HStack{
Text("Following:0").foregroundColor(.white)
Text("Followers:2").foregroundColor(.white)
Text("Kids:0").foregroundColor(.white)
}
}
.padding(20)
.multilineTextAlignment(.center)
}
.frame(width: 450, height: 250)
}
Spacer()
}
}
My Code as to how am inserting my views in the tab view not showing it all as no need just a snippet
struct ContentView: View {
var body: some View {
TabView{
HomeView() .tabItem {
Image(systemName: "house")
Text("Home")
}
ProfileView()
.tabItem {
Image(systemName: "person.circle.fill")
Text("Profile")
}
StatsView()
.tabItem {
Image(systemName: "plus").renderingMode(.original).padding()
Text("Plus")
}
}
Even on a real device it is doing the same so I no its not just the simlutor.
The reason is there is only one VStack so it will be default the main layout of the view. Default VStack if has only one view is from center view.
You just need add another VStack to cover your VStack then it will layout from top
var body: some View {
VStack {
VStack() {
Text("Welcome")
.font(.largeTitle)
.foregroundColor(.black)
ZStack {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(.red)
VStack {
Image("murry")
.clipShape(Circle())
.shadow(radius: 10)
.overlay(Circle()
.stroke(Color.red, lineWidth: 5))
Text("Murry GoldBerg")
HStack{
Text("Following:0").foregroundColor(.white)
Text("Followers:2").foregroundColor(.white)
Text("Kids:0").foregroundColor(.white)
}
}
.padding(20)
.multilineTextAlignment(.center)
}
.frame(width: 450, height: 250)
Spacer()
}
}
}

How to create a 2 column grid with square shaped cells in SwiftUI

I'm trying to replicate this UI in SwiftUI using a Grid.
I created the cell like this.
struct MenuButton: View {
let title: String
let icon: Image
var body: some View {
Button(action: {
print(#function)
}) {
VStack {
icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
Text(title)
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
.multilineTextAlignment(.center)
.padding(.top, 10)
}
}
.frame(width: 160, height: 160)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.fr_primary, lineWidth: 0.6))
}
}
And the Grid like so.
struct LoginUserTypeView: View {
private let columns = [
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20)
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 30) {
ForEach(Menu.UserType.allCases, id: \.self) { item in
MenuButton(title: item.description, icon: Image(item.icon))
}
}
.padding(.horizontal)
.padding()
}
}
}
But on smaller screens like the iPod, the cells are overlapped.
On bigger iPhone screens, still the spacing is not correct.
What adjustments do I have to make so that in every screen size, the cells would show in a proper square shape and equal spacing on all sides?
MenuButton has fixed width and height, thats why it behaves incorrectly.
You could utilise .aspectRatio and .frame(maxWidth: .infinity, maxHeight: .infinity) for this:
struct MenuButton: View {
let title: String
let icon: Image
var body: some View {
Button(action: {
print(#function)
}) {
VStack(spacing: 10) {
icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 60, maxHeight: 60)
Text(title)
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
.multilineTextAlignment(.center)
}
.padding()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fill)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color. fr_primary, lineWidth: 0.6))
}
}

SwiftUI: padding above my view, for no reason?

I am trying to create an Instagram-like UI with SwiftUI, and since I wasn't able to resize the tab elements in the TabView, I decided to write a simple CustomTabView instead. But I end up with a padding at the top of it and I don't understand why. Here is the code:
struct ContentView: View {
#State private var selectedIndex: Int = 0
var body: some View {
VStack {
switch selectedIndex {
case 0:
Color.blue
case 1:
Color.yellow
case 2:
Color.red
case 3:
Color.orange
default:
Color.green
}
CustomTabView(selectedIndex: $selectedIndex)
}
}
}
struct CustomTabView: View {
#Binding var selectedIndex: Int
var body: some View {
VStack {
Divider()
HStack {
Button(action: {
selectedIndex = 0
}, label: {
Image("HomeIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 1
}, label: {
Image("PlayIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 2
}, label: {
Image("AddIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 3
}, label: {
Image("HeartIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 4
}, label: {
Image("ProfileIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.cornerRadius(15)
})
}
.padding(.horizontal, 24)
.padding(.top, 4)
}
.background(Color.white)
}
}
The result I'm getting:
What am I doing wrong?
Thank you for your help
If the problem is the gap between the blue box and the divider, try setting the spacing of the VStack to 0:
VStack(spacing: 0) {
...
}
Top Space
That is the safe area. You can ignore it with this modifier:
.ignoresSafeArea()
Apply it to the view you want it to extend beyond the safe area, for example:
Color.blue
.ignoresSafeArea(.container, edges: .top)
Bottom Space
That is the spacing of the VStack. Get rid of it by setting that to 0:
VStack(spacing: 0) {
,,,
}
try this
Image("background").resizable()
.scaledToFill()
.clipped()
.edgesIgnoringSafeArea([.top])

Center View horizontally in SwiftUI

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.

Resources