SwiftUI how to pass data to previous screen on dismiss - ios

I want pass data on dismissing of presentViewController to previous screen. Here I would like to use block to pass data to previous screen as UIKitApp. But I'm not getting idea to pass data. What are the options we have to pass data to back?
struct ContentView: View {
#State var showModel = false
var body: some View {
VStack {
Button(action: {
showModel.toggle()
}, label: {
Text("Show filters")
}).sheet(isPresented: $showModel, content: {
FilterView()
})
}
}
}
struct FilterView: View {
#Environment(\.presentationMode) var presentationMode
var onDismiss: ((_ model: Filter) -> Void)?
var body: some View {
VStack {
Button(action: {
// Pass data from here to ContentView
let filter = Filter(fromDate: "10/07/2021", toDate: "12/07/2021")
onDismiss?(filter)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Applay Filters")
}).padding()
}.padding()
}
}
struct Filter {
var fromDate: String
var toDate: String
}

You can use #Binding for that (or #StateObject, #ObservedObject, #Environmentobject with #ObservableObject using MVVM Design Pattern)
The code below is an example using #Binding.
Added/Edited Lines
Text("\(filter.fromDate) and \(filter.toDate)") // to see the changed values
#State var filter = Filter(fromDate: "", toDate: "") // in ContentView
#Binding var filter: Filter // in FilterView
FilterView(filter: $filter) // $ used for #Binding parameter
Full Code
struct ContentView: View {
#State var showModel = false
#State var filter = Filter(fromDate: "", toDate: "")
var body: some View {
VStack {
Text("\(filter.fromDate) and \(filter.toDate)")
Button(action: {
showModel.toggle()
}, label: {
Text("Show filters")
}).sheet(isPresented: $showModel, content: {
FilterView(filter: $filter)
})
}
}
}
struct FilterView: View {
#Environment(\.presentationMode) var presentationMode
var onDismiss: ((_ model: Filter) -> Void)?
#Binding var filter: Filter
var body: some View {
VStack {
Button(action: {
// Pass data from here to ContentView
filter = Filter(fromDate: "10/07/2021", toDate: "12/07/2021")
onDismiss?(filter)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Applay Filters")
}).padding()
}.padding()
}
}
struct Filter {
var fromDate: String
var toDate: String
}

Related

How to use #Binding when view is creating using function in SwiftUI?

I created view using Function and which i am calling from another view (AbcView), I want to perform normal #Binding with that, but not sure how to pass value and create #Binding in function.
In Below code I want to pass selectedPassengerId from AbcView to function topSheetClassViews and perform #Binding whenever value passengerIds in SelectedTitleView is updating so that I can get updated value in AbcView.
import SwiftUI
struct AbcView: View {
#StateObject var abcViewModel: AbcViewModel
#State private var selectedPassengerId: Int?
init(accessibiltyID: String, abcViewModel: AbcViewModel) {
self._abcViewModel = StateObject(wrappedValue: abcViewModel)
}
var body: some View {
VStack(spacing: 0) {
// Some Design
}
.overlay(
TopView((accessibilityID: accessibilityID, content: topSheetClassViews(abcViewModel: abcViewModel), selectedRowID: $selectedPassengerId, rowHeight: $rowHeight),, alignment: .top
)
)
}
}
func topSheetClassViews(abcViewModel: AbcViewModel) -> [AnyView] {
var views: [AnyView] = []
for passenger in 0..<abcViewModel.Passengers.count {
views.append(TopSheetPassengerInfoView(abcViewModel: abcViewModel, index: passenger).convertToAnyView())
}
return views
}
struct SelectedTitleView: View {
#ObservedObject var abcViewModel: AbcViewModel
var passengerIds: Int
var body: some View {
VStack(alignment: .trailing) {
Text("passengerIds \(passengerIds)") // here getting correct id which I want to pass to AbcView
Text(abcViewModel.passengerTitle(passengerId: passengerIds))
}
}
}
struct TopSheetPassengerInfoView: View {
#ObservedObject var abcViewModel: AbcViewModel
var index: Int
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(abcViewModel.passengers[index].fullName ?? "")
SelectedTitleView(abcViewModel: abcViewModel, passengerIds: Int(abcViewModel.Passengers[index].passengerId ?? "") ?? 0)
}
}
}
Just put it through everywhere you need to pass it, like
here
func topSheetClassViews(abcViewModel: AbcViewModel, selection: Binding<Int?>) -> [AnyView] {
here
views.append(TopSheetPassengerInfoView(abcViewModel: abcViewModel, selectedID: selection, index: passenger).convertToAnyView())
here
struct TopSheetPassengerInfoView: View {
#ObservedObject var abcViewModel: AbcViewModel
#Binding var selectedID: Int?
and so on

Removing multiple sheets after swiping down the last one swiftui

I currently have two sheets in a row and I want them to be dismissed to the view that called the first sheet once the last sheet is dismissed by the user. I am open to not pulling up views as sheets its just the way I learned how to easily pull up new views.
BookView is what I want to be returned after the PickDefinition sheet view has been dismissed.
BookView pulls up AddWord as a sheet.
AddWord is pulled up as a sheet and then in it PickDefinition is pulled up as a sheet.
After PickDefinition is dismissed I would like for it to go back to the BookView
struct BookView: View {
#ObservedObject var book: Book
#State var addingWord = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
List(Array(zip(book.words, book.definitions)), id: \.self.0) { (word, definition) in
Text("\(word) - \(definition)")
}
}
.onAppear(perform: {
DB_Manager().openBook(name: book.name, book: self.book)
})
.navigationBarTitle("\(book.name)")
.navigationBarItems(trailing: Button(action: {
self.addingWord = true
}) {
Image(systemName: "plus")
}
).sheet(isPresented: $addingWord) {
AddWord(book: self.book)
}
}
}
struct AddWord: View {
#ObservedObject var book: Book
#StateObject var currentArray = SaveArray()
#State var addingDefinition = false
#State var word = ""
#State var definition = ""
#State var allDefinitions: [String] = []
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Form {
TextField("Word: ", text: $word)
}
.navigationBarTitle("Add word")
.navigationBarItems(trailing: Button("Add") {
if self.word != "" {
book.words.append(self.word)
getDef(self.word, book, currentArray)
addingDefinition = true
}
}).sheet(isPresented: $addingDefinition) {
PickDefinition(definitions: currentArray, book: book, word: self.word)
}
}
}
struct PickDefinition: View {
#ObservedObject var definitions: SaveArray
var book: Book
var word: String
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
VStack {
List {
ForEach(0 ..< definitions.currentArray.count) { index in
Button("\(self.definitions.currentArray[index])", action: {
print("hello")
DB_Manager().addWords(name: self.book.name, word: self.word, definition: self.definitions.currentArray[index])
book.definitions.append(self.definitions.currentArray[index])
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
.navigationTitle("Choose")
}
}
}
You need to pass addingWord into the AddWord view, and then into the PickDefinition view, using Binding. When PickDefinition disappears, set the passed property to false to hide the AddWord sheet.
struct BookView: View {
var body: some View {
// ...
.sheet(isPresented: $addingWord) {
AddWord(book: self.book, presentAddingWord: $addingWord)
}
}
}
struct AddWord: View {
#Binding var presentAddingWord: Bool
// ...
var body: some View {
// ...
.sheet(isPresented: $addingDefinition) {
PickDefinition(/* ... */, presentAddingWord: $presentAddingWord)
}
}
}
struct PickDefinition: View {
#Binding var presentAddingWord: Bool
// ...
var body: some View {
// ...
.onDisappear {
presentAddingWord = false
}
}
}

SwiftUI having #Environment(\.presentationMode) for dismissing view misbehaving

I have a view with #Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> for a custom dismiss action based on a published value around some logic like this:
.onAppear(perform: {
viewModel.cancellable = viewModel.$shouldPopBack.sink(receiveValue: { shouldPopBackToHome in
if shouldPopBackToHome {
presentationMode.wrappedValue.dismiss()
}
})
})
This view also has another option to present a sheet
Button(action: {
shouldNavigateToQRCodeScanner = true
}, label: {
Text("Scan QR code")
.padding(.horizontal)
}).sheet(isPresented: $shouldNavigateToQRCodeScanner, content: {
QRCodeScannerView()
})
The problem here is when I have #Environment(\.presentationMode) the QRCodeScannerView is been initialized twice, when I remove the presentationMode it works fine.
Update
I've tried the same behaviour in a test project and it's the same
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#State var isPresentd: Bool = false
var body: some View {
NavigationView {
Button(action: {
isPresentd = true
}, label: {
Text("Navigate to Second View")
})
.sheet(isPresented: $isPresentd, content: {
SecondView()
})
}
}
}
struct SecondView: View {
init() {
print("Initialized")
}
var body: some View {
Text("Second View")
}
}
It has the same issue

SwiftUI: popup does not dismiss

In a Swift-UI List (iOS), showing some CoreData Elements, I want to show a popup for each listelement to change an Attribute of the CoreData Element.
In the code below, it is not possible to dismiss the popup.
If I remove the .id(UUID()) from the List, it works fine.
The .id(UUID()) is needed in my app, because I change the predicate "on the fly" and that's the only way I know, to avoid that SwiftUI tries to compare all List-elements of the old and the new result.
This performance issue was discussed in Performance Issue with SwiftUI List
Any Ideas how to solve this?
import Foundation
import SwiftUI
struct ContentView: View {
#FetchRequest(entity: Item.entity(), sortDescriptors: [], predicate: nil) var items: FetchedResults<Item>
var body: some View {
VStack
{ Button("Create Testdata"){createTestdata()}
List(items, id: \.self)
{ item in
Line(item: item)
}.id(UUID())
}
}
}
struct Line: View {
#ObservedObject var item : Item
#State var showSheet = false
var body: some View {
Text(item.text!)
.onLongPressGesture {
self.showSheet.toggle()// = true
}
.popover( isPresented: self.$showSheet,
arrowEdge: .trailing
)
{ Pop(showSheet: self.$showSheet, item: self.item )
}
}
}
struct Pop: View {
#Binding var showSheet: Bool
var item : Item
//#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("CHANGE TO XXXXXXXXX")
.onTapGesture
{ self.item.text = "XXXXXXX"
self.showSheet = false
}
Text("CHANGE TO YYYYYYYYY")
.onTapGesture
{ self.item.text = "YYYYYY"
self.showSheet = false
}
Button("Cancel")
{
#if os(OSX)
NSApp.sendAction(#selector(NSPopover.performClose(_:)), to: nil, from: nil)
#else
//self.presentationMode.wrappedValue.dismiss() // << behaves the same as below
self.showSheet = false
#endif
}
}
}
}

SwiftUI Picker in a Form doesn't show the selected row

I am trying to have a Picker that shows which option is currently selected.
Try out the following code which correctly selects the right option but the picker does not show which option is selected:
import SwiftUI
struct ContentView: View {
#State var selectedIndex: Int = 0
let strings: [String] = {
var strings: [String] = []
for i in 0..<10 {
strings.append("\(i)")
}
return strings
}()
var body: some View {
NavigationView {
VStack {
Form {
Picker(selection: $selectedIndex,
label: Text("Selected string: \(strings[selectedIndex])")) {
ForEach(0..<strings.count) {
Text(self.strings[$0]).tag($0)
}
}
}
}
.navigationBarTitle("Form Picker",
displayMode: NavigationBarItem.TitleDisplayMode.inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Anyone know what could be wrong? It's observed using Xcode 11.1 and iOS 13.1
I created the simple picker I call "ListPicker" which should fit the bill. I've written it so it works well in a Form; if you need it outside of a Form you will have to tinker with it. If you see any way to improve the code, please add a comment; this is still a learning experience for all of us.
// MARK: - LIST PICKER (PUBLIC)
struct ListPicker<Content: View>: View {
#Binding var selectedItem: Int
var label: () -> Content
var data: [Any]
var selectedLabel: String {
selectedItem >= 0 ? "\(data[selectedItem])" : ""
}
var body: some View {
NavigationLink(destination: ListPickerContent(selectedItem: self.$selectedItem, data: self.data)) {
ListPickerLabel(label: self.label, value: "\(self.selectedLabel)")
}
}
}
// MARK: - INTERNAL
private struct ListPickerLabel<Content: View>: View {
let label: () -> Content
let value: String
var body: some View {
HStack(alignment: .center) {
self.label()
Spacer()
Text(value)
.padding(.leading, 8)
}
}
}
private struct ListPickerContentItem: View {
let label: String
let index: Int
let isSelected: Bool
var body: some View {
HStack {
Text(label)
Spacer()
if isSelected {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}.background(Color.white) // so the entire row is selectable
}
}
private struct ListPickerContent: View {
#Environment(\.presentationMode) var presentationMode
#Binding var selectedItem: Int
var data: [Any]
var body: some View {
List {
ForEach(0..<data.count) { index in
ListPickerContentItem(label: "\(self.data[index])", index: index, isSelected: index == self.selectedItem).onTapGesture {
self.selectedItem = index
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
}
Then you can use it like this:
#State var selectedCar: Int = 0
let cars = ["Jaguar", "Audi", "BMW", "Land Rover"]
Form {
ListPicker(
selectedItem: self.$selectedCar,
label: {
Text("Cars")
},
data: self.cars
)
}

Resources