SwiftUI Form not positioning correctly using Spacer() - ios

I am trying to create a profile page, in which there is a form at the bottom with various options. However when I insert a Spacer() into the VStack, the form does not move to the bottom of the screen, as it should do. I tried replacing the Form with a Text and it worked fine, moving to the bottom of the screen. So I'm assuming it has something to do with the form.
Here is my code
struct Profile: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
NavigationView {
VStack {
Image(systemName: "person.crop.circle")
.resizable()
.frame(width: 50, height: 50)
// .padding(.top)
Text("email#email.com")
.font(.title)
Spacer()
Form {
Section {
//menuListItem(image: "gear", label: "Settings")
menuListItem(image: "questionmark.circle", label: "Help") menuListItem(image: "info.circle", label: "About")
}
Section {
HStack {
Spacer()
Button(action: {
UserDefaults.standard.set(false, forKey: "LoggedIn")
UserDefaults.standard.set(nil, forKey: "user_id")
UserDefaults.standard.set(nil, forKey: "school_id")
self.mode.wrappedValue.dismiss()
}) {
Text("Log Out")
.font(.body)
.foregroundColor(.red)
}
Spacer()
}
}
}
}
.navigationBarTitle("Profile", displayMode: .inline)
}
}
}
struct menuListItem: View {
var image: String
var label: String
var body: some View {
HStack {
Image(systemName: image)
Text(label)
.font(.body)
}
}
}

As long as bottom of the Form is unknown(because Form is scrollable and its items start from the top), swiftUI does not know how much space should put between those two views. So Spacer() does not help.
Try this:
Give your form: for example, .frame(height: 500) or .padding(.top, 400) modifier.

Related

SwiftUI Picker in Form - Can't Dynamically Size the Form Space

I'm struggling with a view where I want to have multiple pickers embedded in
other views. When I wrap the pickers in a Form, I get the desired behavior for the
picker but there is a lot of extra space around the pickers that I can't seem to
automatically adjust.
This is an example - the space in the red outline seems to be determined by the other
view elements not the size of the picker.
I can, of course, hard-code a frame height for the Form but that is trial and error
and would only be specific to the device and orientation. I have tried multiple
versions of Stacks inside Stacks with padding, GeometryReader etc, but I have not come up with any
solution. As an aside, I DO want the picker labels, otherwise I could just remove
the Form.
I also tried setting UITableView.appearance().tableFooterView in an init() but that did not work either.
Here is a simplified version:
struct ContentView4: View {
#State var selectedNumber1: Int = 1
#State var selectedNumber2: Int = 2
#State var selectedNumber3: Int = 3
var body: some View {
NavigationView {
VStack(alignment: .leading) {
HStack {
Spacer()
Text("Compare up to 3")
.font(.caption)
Spacer()
}//h
Form {//for pickers
Picker(selection: $selectedNumber1, label: Text("A")) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
Picker(selection: $selectedNumber2, label: Text("B")) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
Picker(selection: $selectedNumber3, label: Text("C")) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
}//form for pickers
.padding(.horizontal, 10)
//.frame(height: 200) //don't want to hard code this
VStack(alignment: .leading) {
HStack {
Text("A")
.frame(width: 100)
Text("B")
.frame(width: 100)
Text("C")
.frame(width: 100)
}
.padding(.horizontal, 10)
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading){
Text("A title line")
.font(.headline)
.padding(.vertical, 5)
HStack {
Text("Number")
.frame(width: 100)
Text("Number")
.frame(width: 100)
Text("Number")
.frame(width: 100)
}
Text("Another title line")
.font(.headline)
.padding(.vertical, 5)
HStack {
Text("Something")
.frame(width: 100)
Text("Something")
.frame(width: 100)
Text("Something")
.frame(width: 100)
}
Text("A Third title line")
.font(.headline)
.padding(.vertical, 5)
HStack {
Text("More")
.frame(width: 100)
Text("More")
.frame(width: 100)
Text("More")
.frame(width: 100)
}
}
}//scroll
.padding(.horizontal, 10)
}
.navigationBarTitle("Compare Three", displayMode: .inline)
}
}//nav
}//body
}//struct
Interestingly, I am able to get a solution by removing the form and wrapping each
picker in a menu, like this:
Menu {
Picker(selection: $selectedNumber2, label: EmptyView()) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
} label: {
HStack {
Text("B")
Spacer()
Image(systemName: "chevron.right")
.resizable()
.frame(width: 14, height: 14)
}//h
}//menu label
However, I still like the look of the Form better if I could automatically configure
the space around the Form items.
Any guidance would be appreciated. Xcode 13.4, iOS 15.5
Form (and List) is not meant to be stacked inside other views like this, which is why it has such strange behavior.
Thankfully, it's fairly simple to recreate the stuff you do want using NavigationLink. Here’s a quick example of a couple custom views that do just that:
// drop-in NavigationLink replacement for Picker
struct NavigationButton<Content: View, SelectionValue: Hashable> : View {
#Binding var selection: SelectionValue
#ViewBuilder let content: () -> Content
#ViewBuilder let label: () -> Text
var body: some View {
NavigationLink {
PickerView(selection: $selection, content: content, label: label)
} label: {
HStack {
label()
Spacer()
Text(String(describing: selection))
.foregroundColor(.secondary)
}
.contentShape(Rectangle())
}
.buttonStyle(NavigationLinkButtonStyle())
}
}
// subview for the Picker page, which lets us use `dismiss()`
// to pop the subview when the user selects an option
struct PickerView<Content: View, SelectionValue: Hashable> : View {
#Binding var selection: SelectionValue
#ViewBuilder let content: () -> Content
#ViewBuilder let label: () -> Text
#Environment(\.dismiss) private var dismiss
var body: some View {
Form {
Picker(selection: $selection, content: content, label: label)
.pickerStyle(.inline)
.labelsHidden()
.onChange(of: selection) { _ in
dismiss()
}
}
.navigationTitle(label())
}
}
// recreate the appearance of a List row
struct NavigationLinkButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
.frame(maxWidth: .infinity)
Image(systemName: "chevron.right")
.font(.footnote.bold())
.foregroundColor(Color(UIColor.tertiaryLabel))
}
.padding()
.background(
Rectangle()
.fill(configuration.isPressed ? Color(UIColor.quaternaryLabel) : Color(UIColor.systemBackground))
)
}
}
If you like the .insetGrouped style you got using Form, we can replicate that by putting NavigationButton inside a clipped VStack:
VStack(spacing: 0) {
NavigationButton(selection: $selectedNumber1) {
ForEach(0..<10) {
Text("\($0)")
}
} label: {
Text("A")
}
Divider()
NavigationButton(selection: $selectedNumber2) {
ForEach(0..<10) {
Text("\($0)")
}
} label: {
Text("B")
}
}
.clipShape(RoundedRectangle(cornerRadius: 11))
.padding()
.background(Color(UIColor.systemGroupedBackground))
And here’s a screenshot showing my custom views above your original Form.
(And if you like Picker as a popup menu, you could use Menu instead of NavigationLink)

Implementing custom sidebar across all views - SwiftUI

OUTLINE
I have made a custom slimline sidebar that I am now implementing across the whole app. The sidebar consists of a main button that is always showing and when pressed it shows or hides the rest of the sidebar that consists of buttons navigating to other views.
I am currently implementing the sidebar across the app on each view by creating a ZStack like this:
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
SideBarCustom()
Text("Hello, World!")
}
}
}
PROBLEM
I am planning on adding a GeometryReader so if the side bar is shown the rest of the content moves over. With this in mind, the way I am implementing the sidebar on every view feels clunky and a long winded way to add it. Is there a more simple/better method to add this to each view?
Sidebar Code:
struct SideBarCustom: View {
#State var isToggle = false
var names = ["Home", "Products", "Compare", "AR", "Search"]
var icons = ["house.fill", "printer.fill.and.paper.fill", "list.bullet.rectangle", "arkit", "magnifyingglass"]
var imgSize = 20
var body: some View {
GeometryReader { geo in
VStack {
Button(action: {
self.isToggle.toggle()
}, label: {
Image("hexagons")
.resizable()
.frame(width: 40, height: 40)
.padding(.bottom, 20)
})
if isToggle {
ZStack{
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.red)
.frame(width: 70, height: geo.size.height)
VStack(alignment: .center, spacing: 60) {
ForEach(Array(zip(names, icons)), id: \.0) { item in
Button(action: {
// NAVIIGATE TO VIEW
}, label: {
VStack {
Image(systemName: item.1)
.resizable()
.frame(width: CGFloat(imgSize), height: CGFloat(imgSize))
Text(item.0)
}
})
}
}
}
}
}
}
}
}
I don't think there's necessarily a reason to use GeometryReader here. The following is an example that has a dynamic width sidebar (although you could set it to a fixed value) that slides in and out. The main content view resizes itself automatically, since it's in an HStack:
struct ContentView : View {
#State private var sidebarShown = false
var body: some View {
HStack {
if sidebarShown {
CustomSidebar(sidebarShown: $sidebarShown)
.frame(maxHeight: .infinity)
.border(Color.red)
.transition(sidebarShown ? .move(edge: .leading) : .move(edge: .trailing) )
}
ZStack(alignment: .topLeading) {
MainContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
if !sidebarShown {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
}
}
}
}
}
struct CustomSidebar : View {
#Binding var sidebarShown : Bool
var body: some View {
VStack {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
Spacer()
Text("Hi")
Text("There")
Text("World")
Spacer()
}
}
}
struct MainContentView: View {
var body: some View {
VStack {
Text("Main content")
}
}
}

SF Symbol Not Showing on a button in swiftUI

I am very new at IOS dev, this is my day one. so I have been playing with SF symbols and tried to add it into a button, but somehow it doesn't appear at the button
The Bug
The color should be black and it should've appear on the button.
Perhaps I made a beginner mistake, but still don't know ho two fix it. so what did I do wrong?
struct ButtonBig: View {
var ButtonText:String
var ButtonIcon:String
var body: some View {
ZStack{
RoundedRectangle(cornerRadius: 25.0)
.frame(width:117, height: 112.36, alignment:.center)
.foregroundColor(.blue)
VStack{
Image(systemName: ButtonIcon).padding()
Text(ButtonText).foregroundColor(.white)
}
}
}
}
}
Button(action: ) {
ButtonBig(ButtonText: "Button3",ButtonIcon: "calendar")
}
Add button style to PlainButtonStyle()
Button(action: ) {
ButtonBig(ButtonText: "Button3",ButtonIcon: "calendar")
}.buttonStyle(PlainButtonStyle()) //<-- Here
Or you can set renderingMode mode to original.
// Other code
VStack{
Image(systemName: ButtonIcon).renderingMode(.original).padding() //<--- Here
Text(ButtonText).foregroundColor(.white)
}
// Other code
You can do it by overlaying your button image and text on the RoundedRectangle shape view like below :
struct ButtonBig: View {
var ButtonText:String
var ButtonIcon:String
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.frame(width:117, height: 112.36, alignment:.center)
.foregroundColor(.blue)
.overlay(
VStack{
Image(systemName: ButtonIcon).renderingMode(.original).padding()
Text(ButtonText).foregroundColor(.white)
}
)
}
}
}
Or you can also do it by just setting the button style directly to HStack of the button.
HStack {
Button(action: {
}, label: {
ButtonBig(ButtonText: "Button1",ButtonIcon: "calendar")
})
Button(action: {
}, label: {
ButtonBig(ButtonText: "Button2",ButtonIcon: "calendar")
})
Button(action: {
}, label: {
ButtonBig(ButtonText: "Button3",ButtonIcon: "calendar")
})
}.buttonStyle(PlainButtonStyle()) //Set the button style to HStack instead of each button

How to add navigation button link in button in SwiftUI?

I want to nagivate to the details screen using button. the buttons are designed in a separate view and called Using ForEach function. Please guide me to solve this.
This is the code I designed for button
struct ButtonsDesignView: View {
var buttons: MyModel.buttons
var body: some View {
Button(action: { }) {
ZStack {
HStack{
VStack{
Text(btn.title)
.foregroundColor(Color(.blue))
.padding()
Text(btn.subtitle)
.foregroundColor(Color(.blue))
.padding()
}
Spacer()
VStack{
Image(systemName: (btn.image))
.resizable()
.scaledToFit()
.foregroundColor(Color(.blue))
.padding()
}
}
}
}
This is code code I've place in ContentView to call display the buttons in scrollview Using ForEach.
ScrollView(.vertical, showsIndicators: false)
{
ForEach(buttonsData) {
item in
NavigationLink(destination: DetailView(buttons: item))
{
ButtonsDesignView(buttons: item)
.padding(10)
}
}.padding(.horizontal, 30)
}
But with this code navigation link is not working.
You should add navigationLink to ButtonDesignView.
I think that this should work;
NavigationLink(destination: DetailView(buttons: buttons)) {
Button(action: { }) {
ZStack {
HStack{
VStack{
Text(btn.title)
.foregroundColor(Color(.blue))
.padding()
Text(btn.subtitle)
.foregroundColor(Color(.blue))
.padding()
}
Spacer()
VStack{
Image(systemName: (btn.image))
.resizable()
.scaledToFit()
.foregroundColor(Color(.blue))
.padding()
}
}
}
}
}

How to increase tappable area of navigationBarItem in SwiftUI?

I have this code for my view
struct ContentView: View {
var body: some View {
NavigationView{
List{
ForEach(0...5, id: \.self) { note in
VStack(alignment: .leading) {
Text("title")
Text("subtitle")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
.navigationBarItems(trailing: resetButton)
.navigationBarTitle(Text("Notes"))
}
}
var resetButton: some View {
Button(action: {
print("reset")
}) {
Image(systemName: "arrow.clockwise")
}
.background(Color.yellow)
}
}
resetButton looks like this:
When I am tapping the resetButton, it seems like only the yellow area responds to touches.
How do I make tappable area of this button bigger? (Make it behave like a normal UIBarButtonItem)
You can change the frame of the view inside the button:
var resetButton: some View {
Button(action: {
print("reset")
}) {
Image(systemName: "arrow.clockwise")
.frame(width: 44, height: 44) // Or any other size you like
}
.background(Color.yellow)
}
This blog post pointed me in the right direction, I needed to add some padding directly to the Image.
Button(action: {
// Triggered code
}) {
Image(systemName: "plus")
.font(.system(size: 22, weight: .regular))
.padding(.vertical)
.padding(.leading, 60)
}
.background(Color.red) // Not needed

Resources