SwiftUI VStack without the space - swiftui

I am trying to create a view which has a small form at the top with a text field. The results will display below, but I want the list to display directly under the form (with just a little gap) rather than half way down the screen.
I have tried adding Spacer() but cannot get the list to be directly under the form.
import SwiftUI
struct TestView: View {
#State var location = ""
var body: some View {
VStack() {
Form() {
Text("Location")
TextField("Location", text: $location)
Button("Look up") {
// Lookup data
}
}
/* Results will display in the list */
List {
Text("Location One")
Text("Location Two")
Text("Location Three")
Text("Location Four")
Text("Location Five")
Text("Location Six")
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}

You can put the List inside the Form and separate them using Section. Plus no need for a VStack.
struct TestView: View {
#State var location = ""
var body: some View {
Form {
Text("Location")
TextField("Location", text: $location)
Button("Look up") {
// Lookup data
}
Section {
List {
Text("Location One")
Text("Location Two")
Text("Location Three")
Text("Location Four")
Text("Location Five")
Text("Location Six")
}
}
}
}
}

Related

Changing a TextField text for items in a foreach NavigationLink SwiftUI and saving it

There is a list in on the Main View that has navigation links that bring you to a an Edit Birthday View where the textFieldName is saved with the onAppear method. I need help in allowing the user to change the text in the text field on the Edit Birthday View and having it save when the user dismisses and returns to that particular item in the foreach list. I have tried onEditingChanged and on change method but they don't seem to work. (Also, in my view model i append birthday items when they are created in the Add Birthday View). If you would like to see more code i will make updates. Thank you.
/// MAIN VIEW
import SwiftUI
struct MainView: View {
#EnvironmentObject var vm: BirthdayViewModel
#State var nameTextField: String = ""
var body: some View {
VStack(spacing: 20) {
List {
ForEach(vm.searchableUsers, id: \.self) { birthday in
NavigationLink(destination: EditBirthdayView(birthday: birthday)) {
BirthdayRowView(birthday: birthday)
}
.listRowSeparator(.hidden)
}
.onDelete(perform: vm.deleteBirthday)
}
}
.toolbar {
ToolbarItem {
NavigationLink(destination: AddBirthdayView(textfieldName: $nameTextField)) {
Image(systemName: "plus.circle")
}
}
}
}
}
/// EDIT BIRTHDAY VIEW
import SwiftUI
import Combine
struct EditBirthdayView: View {
#EnvironmentObject var vm: BirthdayViewModel
#State var textfieldName: String = ""
#Environment(\.presentationMode) var presentationMode
var birthday: BirthdayModel
var body: some View {
NavigationView {
VStack {
TextField("Name...", text: $textfieldName)
}
Button {
saveButtonPressed()
} label: {
Text("Save")
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()) {
textfieldName = birthday.name
}
}
}
}
func saveButtonPressed() {
vm.updateItem(birthday: birthday)
presentationMode.wrappedValue.dismiss()
}
func updateTextField() {
textfieldName = birthday.name
}
}
struct MainView: View {
#EnvironmentObject var store: BirthdayStore
var body: some View {
List {
ForEach($store.birthdays) { $birthday in
NavigationLink(destination: EditBirthdayView(birthday: $birthday)) {
BirthdayRowView(birthday: birthday)
}
}
.onDelete(perform: deleteBirthday)
}
.listRowSeparator(.hidden)
.toolbar {
ToolbarItem {
NavigationLink(destination: AddBirthdayView() {
Image(systemName: "plus.circle")
}
}
}
}
}
struct EditBirthdayView: View {
#EnvironmentObject var store: BirthdayStore
#Binding var birthday: Birthday
...
TextField("Name", text: $birthday.name)

(SwiftUI) NavigationBar height issue

I have to use large title (navigationBarTitleDisplayMode = .large) and .searchable in View 2.
But in that case, the height of the navigation bar in View 3 is set strangely.
I think, the sum of the height of the navigation bar and the height of the search bar in View 2 is applied to the height of the navigation bar in View 3.
Is there a way to set the height of the navigation bar in view 3 to the height it would have when .inline?
import SwiftUI
struct ContentView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.red
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
var body: some View {
NavigationView {
ScrollView {
VStack {
NavigationLink("View 2") {
SecondView()
}
}
}
.navigationTitle("View 1")
.navigationBarTitleDisplayMode(.large)
}
}
}
struct SecondView: View {
#State var text = ""
var body: some View {
ScrollView {
VStack {
NavigationLink("View 3") {
ThirdView()
}
}
}
.navigationTitle("View 2")
.navigationBarTitleDisplayMode(.large)
.searchable(text: $text)
}
}
struct ThirdView: View {
var body: some View {
Text("View 3")
.navigationTitle("View 3")
.navigationBarTitleDisplayMode(.inline)
}
}
If you remove the first and second .navigationBarTitleDisplayMode(.large) & change your .searchable to .searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always)) it should work.
Tested and working code:
struct FirstView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .red
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
var body: some View {
NavigationStack {
List {
NavigationLink("View 2") {
SecondView()
}
}.navigationBarTitle("View 1")
}
}
}
struct SecondView: View {
#State var text: String = ""
var body: some View {
List {
NavigationLink("View 3") {
ThirdView()
}
}.navigationTitle("View 2")
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always))
}
}
struct ThirdView: View {
var body: some View {
Text("View 3")
.navigationTitle("View 3")
.navigationBarTitleDisplayMode(.inline)
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}

Popover displaying inaccurate information inside ForEach

I'm having a problem where I have a ForEach loop inside a NavigationView. When I click the Edit button, and then click the pencil image at the right hand side on each row, I want it to display the text variable we are using from the ForEach loop. But when I click the pencil image for the text other than test123, it still displays the text test123 and I have absolutely no idea why.
Here's a video. Why is this happening?
import SwiftUI
struct TestPopOver: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var showThemeEditor = false
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.showThemeEditor = true
}
}
}
}
}
.popover(isPresented: $showThemeEditor) {
CustomPopOver(isShowing: $showThemeEditor, text: text)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
struct CustomPopOver: View {
#Binding var isShowing: Bool
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.isShowing = false
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
This is a very common issue (especially since iOS 14) that gets run into a lot with sheet but affects popover as well.
You can avoid it by using popover(item:) rather than isPresented. In this scenario, it'll actually use the latest values, not just the one that was present when then view first renders or when it is first set.
struct EditItem : Identifiable { //this will tell it what sheet to present
var id = UUID()
var str : String
}
struct ContentView: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var editItem : EditItem? //the currently presented sheet -- nil if no sheet is presented
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.editItem = EditItem(str: text) //set the current item
}
}
}
}
}
.popover(item: $editItem) { item in //item is now a reference to the current item being presented
CustomPopOver(text: item.str)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct CustomPopOver: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.presentationMode.wrappedValue.dismiss()
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
I also opted to use the presentationMode environment property to dismiss the popover, but you could pass the editItem binding and set it to nil as well (#Binding var editItem : EditItem? and editItem = nil). The former is just a little more idiomatic.

Swiftui: ForEach button, wrong parameter is passed in a view [duplicate]

I have an issue using a sheet inside a ForEach. Basically I have a List that shows many items in my array and an image that trigger the sheet. The problem is that when my sheet is presented it only shows the first item of my array which is "Harry Potter" in this case.
Here's the code
struct ContentView: View {
#State private var showingSheet = false
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(0 ..< movies.count) { movie in
HStack {
Text(self.movies[movie])
Image(systemName: "heart")
}
.onTapGesture {
self.showingSheet = true
}
.sheet(isPresented: self.$showingSheet) {
Text(self.movies[movie])
}
}
}
}
}
}
There should be only one sheet, so here is possible approach - use another sheet modifier and activate it by selection
Tested with Xcode 12 / iOS 14 (iOS 13 compatible)
extension Int: Identifiable {
public var id: Int { self }
}
struct ContentView: View {
#State private var selectedMovie: Int? = nil
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(0 ..< movies.count) { movie in
HStack {
Text(self.movies[movie])
Image(systemName: "heart")
}
.onTapGesture {
self.selectedMovie = movie
}
}
}
.sheet(item: self.$selectedMovie) {
Text(self.movies[$0])
}
}
}
}
I changed your code to have only one sheet and have the selected movie in one variable.
extension String: Identifiable {
public var id: String { self }
}
struct ContentView: View {
#State private var selectedMovie: String? = nil
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(movies) { movie in
HStack {
Text(movie)
Image(systemName: "heart")
}
.onTapGesture {
self.selectedMovie = movie
}
}
}
.sheet(item: self.$selectedMovie, content: { selectedMovie in
Text(selectedMovie)
})
}
}
}
Wanted to give my 2 cents on the matter.
I was encountering the same problem and Asperi's solution worked for me.
BUT - I also wanted to have a button on the sheet that dismisses the modal.
When you call a sheet with isPresented you pass a binding Bool and so you change it to false in order to dismiss.
What I did in the item case is I passed the item as a Binding. And in the sheet, I change that binding item to nil and that dismissed the sheet.
So for example in this case the code would be:
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(0 ..< movies.count) { movie in
HStack {
Text(self.movies[movie])
Image(systemName: "heart")
}
.onTapGesture {
self.selectedMovie = movie
}
}
}
.sheet(item: self.$selectedMovie) {
Text(self.movies[$0])
// My addition here: a "Done" button that dismisses the sheet
Button {
selectedMovie = nil
} label: {
Text("Done")
}
}
}
}

SwiftUI list line separatorStyle not work correctly with navigation

I have try to hide and show separator line on 2 views
the first view I implement as below
struct FirstView: View {
var body: some View {
NavigationView{
VStack{
List{
Text("test1")
Text("test2")
}
.onAppear {UITableView.appearance().separatorStyle = .none}
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
NavigationLink(destination: NextView()) {
Text("next view")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
This will display none line on FirstView.
when navigate to NextView which also have list the separate line is not show up.
After that navigate back to FirstView the separate line show up.
struct NextView: View {
var body: some View {
List{
Text("test1")
Text("test2")
}
}
}
I am not sure is this bug or I coded wrong on somewhere.
Adding UITableView.appearance().separatorColor = .clear anywhere in your code before the List appears should work.
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}