i would like to remove the space between my back button ("Rezept hinzufügen") and my navigationbarTitle ("Suche")... I cant figure out why this space is there so i need your swarm intelligence. :)
What did i try?
Working with a ZStack, maybe there is NavigationView from before which is doin this bug
Add Spacer()
Recreate the whole view
Now I stuck...
I think the easiest way is to show you my problem with an video...
Here you can see my problem
Here is my code...
import SwiftUI
extension UIApplication
{
func endEditing(_force : Bool)
{
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(_force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier
{
var gesture = DragGesture().onChanged{_ in UIApplication.shared.endEditing(_force: true)}
func body(content: Content) -> some View
{
content.gesture(gesture)
}
}
extension View
{
func resignKeyboardOnDragGesture() -> some View
{
return modifier(ResignKeyboardOnDragGesture())
}
}
/*
Zutaten pflegen Button, zum hinzufügen von Zutaten zu einem Rezept.
**/
struct RecipeIngredientsView: View {
let myArray = ["Dennis", "Tessa", "Peter", "Anna", "Tessa", "Klaus", "Xyan", "Zuhau", "Clown", "Brot", "Bauer"]
#State private var searchText = ""
#State private var showCancelButton: Bool = false
var body: some View {
NavigationView
{
VStack
{
HStack
{
HStack
{
Image(systemName: "magnifyingglass")
TextField("Suche", text: $searchText, onEditingChanged: { isEditing in self.showCancelButton = true}, onCommit: {
print("onCommit")
}).foregroundColor(.primary)
Button(action: {
self.searchText = searchText
}){
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
}
}.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)
if showCancelButton {
Button("Abbrechen")
{
UIApplication.shared.endEditing(_force: true)
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
.padding(.horizontal)
.navigationBarHidden(showCancelButton)
//Gefilterte Liste der Namen aus meinem Array
List {
ForEach(myArray.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self)
{
searchText in Text(searchText)
}
}
.navigationBarTitle(Text("Suche"))
.resignKeyboardOnDragGesture()
}
}
}
}
Thanks for your help!
:-)
Just remove redundant NavigationView - it is needed only one in same view hierarchy, and obviously there is already some in parent view
struct RecipeIngredientsView: View {
let myArray = ["Dennis", "Tessa", "Peter", "Anna", "Tessa", "Klaus", "Xyan", "Zuhau", "Clown", "Brot", "Bauer"]
#State private var searchText = ""
#State private var showCancelButton: Bool = false
var body: some View {
NavigationView // << remove this one !!
{
Related
I have successfully displayed the data to the UI, but I want the user to be able to update my data again when tapping the "Save" button . Hope you can help me!
Profile
I have successfully displayed the data to the UI, but I want the user to be able to update my data again when tapping the "Save" button . Hope you can help me!
There are many ways to achieve what you want. This is just one approach, by
passing the profileViewModel to EditProfile:
class ProfileViewModel: ObservableObject {
#Published var user = Profile(id: "", image: "", birthDay: "", role: [], gender: "", name: "")
private var ref: DatabaseReference = Database.database().reference()
func fetchData(userId: String? = nil) {
// 8hOqqnFlfGZTj1u5tCkTdxAED2I3
ref.child("users").child(userId ?? "default").observe(.value) { [weak self] (snapshot) in
guard let self = self,
let value = snapshot.value else { return }
do {
print("user: \(value)")
self.user = try FirebaseDecoder().decode(Profile.self, from: value)
} catch let error {
print(error)
}
}
}
func saveUser() {
// save the user using your ref DatabaseReference
// using setValue, or updateChildValues
// see https://firebase.google.com/docs/database/ios/read-and-write
}
}
struct EditProfile: View {
#ObservedObject var profileViewModel: ProfileViewModel // <--- here
var body: some View {
VStack {
Text(profileViewModel.user.name) // <--- you probably meant TextField
.font(.custom("Poppins-Regular", size: 15))
.foregroundColor(Color.black)
Text("\(profileViewModel.user.birthDay)!")
.font(.custom("Poppins-Regular", size: 22))
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("\(profileViewModel.user.gender)")
.font(.custom("Poppins-Regular", size: 22))
.fontWeight(.bold)
.foregroundColor(Color.black)
Text(profileViewModel.user.role.first ?? "")
.font(.custom("Poppins-Regular", size: 22))
.fontWeight(.bold)
.foregroundColor(Color.black)
Button(action: {
// save the profileViewModel.user to database
profileViewModel.saveUser() // <--- here
}) {
Text("Save")
}
}
.padding()
}
}
struct CategoriesView: View {
#ObservedObject var viewModel = SectionViewModel()
#EnvironmentObject var loginViewModel : LoginViewModel
#StateObject var profileViewModel = ProfileViewModel()
var body: some View {
ZStack {
VStack (alignment: .leading, spacing:0) {
EditProfile(profileViewModel: profileViewModel) // <--- here
.padding()
.padding(.bottom,-10)
}
}
.onAppear() {
self.viewModel.fetchData()
profileViewModel.fetchData(userId: loginViewModel.session?.uid)
}
}
}
EDIT1: regarding the updated code.
In your new code, in ProfileHost you are not passing ProfileViewModel.
Use:
NavigationLink(destination: ProfileEditor(profileViewModel: viewModel)) {
ProfileRow(profileSetting: profile)
}
And in ProfileEditor replace profile with profileViewModel.user
You will probably need to adjust profileItem and put it in a .onAppear {...} . Something like this:
struct ProfileEditor: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#ObservedObject var profileViewModel: ProfileViewModel
#EnvironmentObject var loginViewModel: LoginViewModel
let profileLabel: [String] = ["Name", "Account", "Gender", "Role", "Email"]
#State var profileItem: [String] = []
#State var profileEditorRow: [ProfileEditorItem] = []
var body: some View {
VStack {
ForEach(profileEditorRow) { editor in
if editor.id == 5 {
ProfileEditorRow(editor: editor, showLastLine: true)
} else {
ProfileEditorRow(editor: editor, showLastLine: false)
}
}
Button("Save") {
profileViewModel.updateData(userId: loginViewModel.session?.uid)
}
}
.onAppear {
profileItem = [profileViewModel.user.name,
profileViewModel.user.birthDay,
profileViewModel.user.gender,
profileViewModel.user.role.first ?? "",
profileViewModel.user.birthDay]
for n in 1...5 {
profileEditorRow.append(ProfileEditorItem(id: n, label: profileLabel[n-1], item: profileItem[n-1]))
}
}
}
}
EDIT2: update func
func updateData() {
ref.("users").child(user.id).updateChildValues([
"name": user.name,
"birthDay": user.birthDay,
"gender": user.gender,
"role": user.role.first ?? ""])
}
and use this in ProfileEditor :
Button("Save") {
profileViewModel.updateData()
}
I'm trying to build an demo app by swiftUI that get multi text from user and add them to the list, below , there is an image of app every time user press plus button the AddListView show to the user and there user can add multi text to the List.I have a problem to add them to the list by new switUI data Flow I don't know how to pass data.(I comment more information)
Thanks 🙏
here is my code for AddListView:
import SwiftUI
struct AddListView: View {
#State var numberOfTextFiled = 1
#Binding var showAddListView : Bool
var body: some View {
ZStack {
Title(numberOfTextFiled: $numberOfTextFiled)
VStack {
ScrollView {
ForEach(0 ..< numberOfTextFiled, id: \.self) { item in
PreAddTextField()
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView)
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false))
}
}
struct PreAddTextField: View {
// I made this standalone struct and use #State to every TextField text be independent
// if i use #Binding to pass data all Texfield have the same text value
#State var textInTextField = ""
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
#Binding var numberOfTextFiled : Int
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
numberOfTextFiled += 1
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
and for DataModel:
import SwiftUI
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
var textData = [
Text1(text: "SwiftUI"),
Text1(text: "Data flow?"),
]
and finally:
import SwiftUI
struct ListView: View {
#State var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Because of the multiple-items part of the question, this becomes a lot less trivial. However, using a combination of ObservableObjects and callback functions, definitely doable. Look at the inline comments in the code for explanations about what is going on:
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
//Store the items in an ObservableObject instead of just in #State
class AppState : ObservableObject {
#Published var textData : [Text1] = [.init(text: "Item 1"),.init(text: "Item 2")]
}
//This view model stores data about all of the new items that are going to be added
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "")] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, text: newValue)
}
}
}
}
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "")) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String //this takes a binding to the view model now
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void //callback function for what happens when "Add" gets pressed
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void //callback function for what happens when the plus button is hit
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
struct ListView: View {
#StateObject var appState = AppState() //store the AppState here
#State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView, appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
I am building a list which should have more than 100 elements (in the future) but the alignment seems not to work..
Currently i have this...
I think it looks pretty nice atm - but the problem is, when you clicked an element (it doesnt matter which one) every element will be clicked... I tested this by print command.
The searchbar works pretty fine, except from hiding the head - but that is not the problem right now..
When you search for an item, the correct item will be displayed and by clicking on it, the correct command appears...
I am looking for my mistake, but i cant find it.. Maybe you guys can help me :-)
This is my code...
struct RecipeIngredientsView: View {
let myArray = ["Dennis", "Tessa", "Peter", "Anna", "Tessa", "Klaus", "Xyan", "Zuhau", "Clown", "Brot", "Bauer"]
#State private var searchText = ""
#State private var showCancelButton: Bool = false
var body: some View {
VStack
{
HStack
{
HStack
{
Image(systemName: "magnifyingglass")
TextField("Suche", text: $searchText, onEditingChanged: { isEditing in self.showCancelButton = true}, onCommit: {
print("onCommit")
}).foregroundColor(.primary)
Button(action: {
self.searchText = searchText
}){
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
}
}.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)
if showCancelButton {
Button("Abbrechen")
{
UIApplication.shared.endEditing(_force: true)
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
.padding(.horizontal)
.navigationBarHidden(showCancelButton)
List {
VStack{
ForEach(myArray.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self)
{
searchText in VStack {
CardView(searchText: searchText)
}
}
Spacer()
}
}
.navigationBarTitle(Text("Suche"))
.resignKeyboardOnDragGesture()
}
}
}
struct CardView : View
{
#State var searchText = ""
var body : some View
{
HStack{
Text(searchText)
Spacer()
Button(action: {print("\(searchText) btn pressed!")})
{
Image(systemName: "circle")
.frame(width:25, height:25)
.clipShape(Circle())
}
}
}
}
Thank you guys & merry x-mas!:-)
This happened because of :
List {
VStack{
//.... ForEach loop
}
}
Like that the list contains just one element , which is the VStack
To avoid this your code should be like that :
List {
ForEach(myArray.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self)
{ searchText in
CardView(searchText: searchText)
}
}
When I tap edit, it will show a delete button (minus icon). When the delete button tapped it will show the orange delete option (as the gif shows). Now I'm trying to reset the state back to the origin (from orange button back to video name and length ) when the Done button is tapped.
I'm trying few options like closures but nothing much.
Any help would be much appreciated!
My child view Video
struct Video: View {
var videoImage : String
var title : String
var duaration : Int
#Binding var deleteActivated : Bool
var body: some View {
HStack {
Image(videoImage)
...
if deleteActivated {
Button(action: {
}) {
ZStack {
Rectangle()
.foregroundColor(.orange)
.cornerRadius(radius: 10, corners: [.topRight, .bottomRight])
Text("Delete")
.foregroundColor(.white)
}
}
} else {
VStack(alignment: .leading){
....
My parent view VideosDirectory
struct VideosDirectory: View {
#State var videos:[DraftVideos] = [
DraftVideos(isSelected: true,title: "Superman workout", duration: 5, imageURL: "test"),
DraftVideos(isSelected: true,title: "Ironman workout", duration: 15, imageURL: "test1"),
DraftVideos(isSelected: true,title: "Ohman workout and long name", duration: 522, imageURL: "test2")
]
init() {
self._deleteActivated = State(initialValue: Array(repeating: false, count: videos.count))
}
#State private var deleteActivated: [Bool] = []
#State private var show = false
// #State private var editing = false
var body: some View {
// VStack {
NavigationView {
ScrollView(.vertical) {
VStack {
ForEach(videos.indices, id: \.self) { i in
HStack {
if self.show {
Button(action: {
withAnimation {
self.deleteActivated[i].toggle()
}
}) {
Image(systemName: "minus.circle.fill")
...
}
}
Video(videoImage: videos[i].imageURL, title: videos[i].title, duaration: videos[i].duration, deleteActivated: $deleteActivated[i])
}
}
}
.animation(.spring())
}
.navigationBarItems(trailing:
HStack {
Button(action: {
self.show.toggle()
}) {
if self.show {
Text("Done")
} else {
Text("Edit")
}
}
})
}
}
}
Provided code is not testable so just an idea:
Button(action: {
self.deleteActivated = Array(repeating: false, count: videos.count)
self.show.toggle()
}) {
or almost the same but as "post-action" in
}
.animation(.spring())
.onChange(of: self.show) { _ in
// most probably condition is not needed here, but is up to you
self.deleteActivated = Array(repeating: false, count: videos.count)
}
I defined 2 popovers and one sheet in the View Line().
Using this view in a VStack, everything works fine.
Using it inside a List, the wrong popovers /sheets are displayed when the corresponding text or Button is tapped.
What's going wrong here?
struct ContentView: View {
var body: some View {
VStack {
Line()
List {
Line()
Line()
Line()
}
}
}
}
struct Line: View {
#State private var showPopup1 = false
#State private var showPopup2 = false
#State private var showSheet2 = false
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Button("Sheet2"){self.showSheet2 = true}
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}
}
struct Popover1: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 1 \(self.showSheet ? "T" : "F")")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Popover2: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 2")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Sheet2: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Sheet 2")
Button("Cancel"){ self.presentation.wrappedValue.dismiss() }
}
}
}
Just don't use Button for .sheet. List detects buttons in row and activate entire row (not sure about bug, let it be as designed). So using only and for everywhere in sub-elements gestures, makes your code work.
Tested with Xcode 11.2 / iOS 13.2
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Text("Sheet2") // << here !!!
.onTapGesture {self.showSheet2 = true} // << here !!!
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}