SwiftUI passing a saved value to parent view - swift

I am fairly new to SwiftUI I am trying to figure out the best way to pass data from a child view to parent?
Thanks for the help I come from a Javascript (React) background so this is a little different for me
The way my child view works is the user clicks on an image to select that image.
I have #State binding that saves the imgUrl which is a String referring to name in Assets.
I am just not sure about the best way to pass that value to the parent component.
Here is the child view (imageSelector)
struct ImageSelector: View {
#State private var windowImgs = ["1", "2", "3","4","5","6","7","8","9","10","11","12","13", "14","15","16","17","18"]
#State private var imgPicked = ""
var body: some View{
ScrollView(Axis.Set.horizontal, showsIndicators: true){
HStack{
ForEach(0..<18){num in
Button(action:{
self.imgPicked = self.windowImgs[num]
print(self.imgPicked)
}){
Image("\(self.windowImgs[num])")
.renderingMode(.original)
.resizable()
.cornerRadius(4)
.frame(width: 100, height: 100)
}
}
}
}
}
}
Here is the parent view (AddCounterForm)
struct AddCounterForm: View {
#Environment(\.presentationMode) var presentationMode
#State private var pickedImg: String = "defaultImg"
#State private var price: String = "0.0"
#State private var qty: String = "0"
var body: some View {
VStack (spacing: 40){
HStack {
Button("Cancel"){
self.presentationMode.wrappedValue.dismiss()
}
.foregroundColor(.red)
Spacer()
Button("Save"){
}
}
HStack {
VStack (spacing: 20){
TextField("Window type", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
TextField("Window location", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
Image(pickedImg)
.resizable()
.cornerRadius(4)
.frame(width: 90, height: 90)
.padding(.leading)
}
HStack {
Text("Price")
TextField("", text:$price)
.frame(width: 70)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
Spacer()
Text("Qty")
TextField("", text:$qty)
.frame(width: 70)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
}
VStack {
Text("Select an image")
.foregroundColor(.blue)
ImageSelector()
.padding(.bottom)
Button("Use your own image"){
//method
}
.frame(width: 180, height: 40)
.background(Color.blue)
.clipShape(Capsule())
.foregroundColor(.white)
.padding(.top)
}
}
.padding()
}
}
Solution for preview thanks for the help from #Asperi & #neverwinterMoon
struct ImageSelector_Previews: PreviewProvider {
static var previews: some View {
PreviewWrapper()
}
}
struct PreviewWrapper: View {
#State(initialValue: "") var imgPicked: String
var body: some View {
ImageSelector(imgPicked: $imgPicked)
}
}

In this case Binding is most appropriate
struct ImageSelector: View {
#Binding var imgPicked: String
and use
ImageSelector(imgPicked: $pickedImg)
.padding(.bottom)

Related

Passing data in PreviewProvider SwiftUI, ObservableObject class, ObservedObject

I guys, I have a class with some properties (class ReservationInfo). In my first screen (ReservationFormView) I create an instance of the class(myReservationInfo).
I pass the data with a NavigationLink to the second view (ReservationRecapView). It works all right, but I have a problem with the SwiftUI preview. How can I pass some example data to the preview provider?
class ReservationInfo: ObservableObject {
#Published var customerName: String = ""
#Published var surname : String = ""
#Published var nPeople : Int = 1
#Published var date = Date()
}
struct ReservationFormView: View {
#StateObject var myReservationInfo = ReservationInfo()
#State private var showSecondView = false
init() {
UIStepper.appearance().setDecrementImage(UIImage(systemName: "minus"), for: .normal)
UIStepper.appearance().setIncrementImage(UIImage(systemName: "plus"), for: .normal)
}
var body: some View {
NavigationView{
VStack(alignment: .center) {
Text("Little lemon")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.gray)
.multilineTextAlignment(.center)
.padding(.bottom, 10.0)
Label("Reservation Form", systemImage: "fork.knife")
.foregroundColor(Color.gray)
.padding(.bottom, 30)
VStack(alignment: .center, spacing: 30.0){
TextField("Type your name", text: $myReservationInfo.customerName, onEditingChanged: {print($0)})
.onChange(of: myReservationInfo.customerName, perform: { newValue in print(newValue)})
.onSubmit {
print("submitted")
}
.underlineTextField()
TextField("Type your surname", text: $myReservationInfo.surname)
.underlineTextField()
HStack(alignment: .center, spacing: 30.0){
Label("\(myReservationInfo.nPeople)", systemImage: "person.fill")
.font(.title2)
Stepper("N di persone", value: $myReservationInfo.nPeople , in: 1...20)
.labelsHidden()
.accentColor(.blue)
}
.padding(.top)
DatePicker(
"Select date",
selection: $myReservationInfo.date,
in: Date.now...,
displayedComponents: [.date, .hourAndMinute]
)
.onChange(of: myReservationInfo.date, perform: {date in print(date)})
.padding(.bottom, 20.0)
}
NavigationLink{
ReservationRecapView(myReservationInfo: myReservationInfo)
} label: {
Text("Avanti")
.padding(.horizontal, 70.0)
}
.padding(.vertical, 10)
.background(Color.blue)
.cornerRadius(10)
.foregroundColor(.white)
}
.padding(.horizontal, 30.0)
.padding(.bottom, 100.0)
.environmentObject(myReservationInfo)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ReservationFormView()
}
}
}
extension Color {
static let darkPink = Color(red: 208 / 255, green: 45 / 255, blue: 208 / 255)
}
extension View {
func underlineTextField() -> some View {
self
.autocorrectionDisabled(true)
.overlay(Rectangle().frame(height: 2).padding(.top, 40))
.foregroundColor(.blue)
}
}
struct ReservationRecapView: View {
#ObservedObject var myReservationInfo : ReservationInfo
var body: some View {
VStack(alignment: .leading, spacing: 10.0){
Text(myReservationInfo.customerName)
Text(myReservationInfo.surname)
Text("Number of people \(myReservationInfo.nPeople)")
Text(myReservationInfo.date, style: .date)
Text(myReservationInfo.date, style: .time)
}.font(.title2)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ReservationRecapView(myReservationInfo: ReservationInfo())
}
}
I don't know how to pass date in the PreviewProvider

SwiftUI Textfield not actually making updates to data

I am trying to make an application where the user can see a list of foods and recipes and make changes etc. In the DetailView for each food, there is an EditView where the values can be changed. I am trying to use a double binding on the value by using #State to define the value and $food.name for example to make the changes happen.
When I click 'Done' and exit this view back to the DetailView, the changes are not made at all, leaving me confused?
Any help on this problem would be greatly appreciated, thank you :)
My EditView.swift:
import SwiftUI
struct EditView: View {
#State var food: Food
var body: some View {
List {
Section(header: Text("Name")) {
TextField("Name", text: $food.name)
}
Section(header: Text("Image")) {
TextField("Image", text: $food.image)
}
Section(header: Text("Desc")) {
TextField("Desc", text: $food.desc)
}
Section(header: Text("Story")) {
TextField("Story", text: $food.story)
}
}.listStyle(InsetGroupedListStyle())
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView(food: cottonCandy)
}
}
My FoodDetail.swift:
import SwiftUI
struct FoodDetail: View {
#State var food: Food
#State private var isPresented = false
var body: some View {
VStack {
Image(food.image)
.resizable()
.frame(width: 300.0,height:300.0)
.aspectRatio(contentMode: .fill)
.shadow(radius: 6)
.padding(.bottom)
ScrollView {
VStack(alignment: .leading) {
Text(food.name)
.font(.largeTitle)
.fontWeight(.bold)
.padding(.leading)
.multilineTextAlignment(.leading)
Text(food.desc)
.italic()
.fontWeight(.ultraLight)
.padding(.horizontal)
.multilineTextAlignment(.leading)
Text(food.story)
.padding(.horizontal)
.padding(.top)
Text("Ingredients")
.bold()
.padding(.horizontal)
.padding(.vertical)
ForEach(food.ingredients, id: \.self) { ingredient in
Text(ingredient)
Divider()
}.padding(.horizontal)
Text("Recipe")
.bold()
.padding(.horizontal)
.padding(.vertical)
ForEach(food.recipe, id: \.self) { step in
Text(step)
Divider()
}.padding(.horizontal)
}.frame(maxWidth: .infinity)
}.frame(minWidth: 0,
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .center
)
}
.navigationBarItems(trailing: Button("Edit") {
isPresented = true
})
.fullScreenCover(isPresented: $isPresented) {
NavigationView {
EditView(food: food)
.navigationTitle(food.name)
.navigationBarItems(leading: Button("Cancel") {
isPresented = false
}, trailing: Button("Done") {
isPresented = false
})
}
}
}
}
struct FoodDetail_Previews: PreviewProvider {
static var previews: some View {
Group {
FoodDetail(food: cottonCandy)
}
}
}
Inside EditView, you want to have a Binding. Replace
#State var food: Food
with
#Binding var food: Food
... then, you'll need to pass it in:
.fullScreenCover(isPresented: $isPresented) {
NavigationView {
EditView(food: $food) /// make sure to have dollar sign
.navigationTitle(food.name)
.navigationBarItems(leading: Button("Cancel") {
isPresented = false
}, trailing: Button("Done") {
isPresented = false
})
}
}

How does $ work in SwiftUI and why can I cast a #StateObject to an #ObservedObject

I'm a newbie developing an app which basically has a fridge class, which contains food. I'm trying to have one single fridge with a list of food. So I set foodList to be a published object and instantiate a #StateObject fridge when the program starts. Basically, from my perspective, whenever the foodList changes, the fridge object will save its state and give me a new view.
In addition, I want to pass this #StateObject fridge to another view so that my another view could modify my foodList in fridge
Below is my code
struct ContentView: View {
#StateObject private var fridge=Fridge();
private var dbStartWith=0;
#State private var selection = 1;
#State private var addFood = false;
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: AddFoodView(fridgeView: fridge)) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
} )
}
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(1)
Text("random tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("profile")
}
.tag(0)
}
}
}
struct FoodView: View{
var body: some View{
NavigationView{
Text("food destination view ");
}
}
}
struct AddFoodView: View{
#ObservedObject var fridgeView: Fridge;
#State private var name = ""
#State private var count : String = "1"
#State private var category : String = "肉类";
#State var showCategory = false
#State var showCount = false
var body: some View{
ZStack{
NavigationView{
Form{
HStack{
Text("食品")
TextField("请输入材料名", text: $name);
}.padding(.bottom,10).padding(.top,10).padding(.leading,10).padding(.trailing,10)
HStack{
Text("数量")
TextField("请选择数量",text:$count,onEditingChanged:{(changed) in
self.hideKeyboard();
self.showCount=changed;
}){}
}.padding(.bottom,10).padding(.top,10).padding(.leading,10).padding(.trailing,10)
HStack{
Text("种类")
TextField("请选择种类",text: $category,onEditingChanged:{(changed) in
self.hideKeyboard();
self.showCategory=changed;
}){}
}.padding(.bottom,10).padding(.top,10).padding(.leading,10).padding(.trailing,10)
}
}.navigationBarItems(trailing: NavigationLink(destination: FoodView()){
Text("保存").foregroundColor(Color.blue).font(.system(size: 18,design: .default))
}).simultaneousGesture(TapGesture().onEnded{
var tempFood=Food(id: fridgeView);//not completed yet
})
ZStack{
if self.showCount{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCount=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $count,label: EmptyView()) {
ForEach(1..<100){ number in
Text("\(number)").tag("\(number)")
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
.offset(x:10,y:-10)
Spacer()
}
if self.showCategory{
let categoryArr = ["肉类","蔬菜类","饮料类","调味品类"]
ZStack{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCategory=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $category,label: EmptyView()) {
ForEach(0..<categoryArr.count){ number in
Text(categoryArr[number]).tag(categoryArr[number])
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
Spacer()
}.offset(x:10,y:20)
}
}
}.animation(.easeInOut)
}
}
Basically I want to pass the #StateObject private var fridge to the struct AddFoodView: View so that my AddFoodView can use the variable. From what I learned online, I think I need to inject #ObservedObjects like this NavigationLink(destination: AddFoodView(fridgeView: fridge)). This doesn't give me error ( at least in debugger phase) which confuses me. Isn't fridge a Food variable and fridgeView a #ObservedObject Food variable ?
If I do something like this AddFoodView(fridgeView: $fridge)). There is an error saying Cannot convert value '$fridge' of type 'ObservedObject<Fridge>.Wrapper' to expected type 'Fridge', use wrapped value instead
#StateObject and #ObservedObject are sources of truth, they are very similar to each other and almost interchangeable.
To share these with other Views you should pass it to the AddFoodView using .environmentObject(fridge) and change the #ObservedObject var fridgeView: Fridge; in AddFoodView to #EnvironmentObject var fridgeView: Fridge.
Look up the Apple Documentation on "Managing Model Data in Your App".
struct BookReader: App {
#StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
struct LibraryView: View {
#EnvironmentObject var library: Library
// ...
}
Another good source are the Apple SwiftUI Tutorials, especially "Handling User Input"

Error: Argument passed to call that takes no arguments SwiftUI

In my code I get this error and I cannot figure out how to resolve it. The error is in the Trailing block of code
struct Map: View {
#ObservedObject public var dataManager = DataManager.shared
#Binding public var seqId: String
// rz- to pass seqId to second view
// #State public var seqId2: String = ""
var body: some View {
ZStack {
VStack {
Text("Results").foregroundColor(Color.white).onAppear{self.dataManager.downloadSeqData(seqId: self.seqId)} .onAppear(perform: playSound)/*rz want to add this sound func to contentview somehow, otherwise it will constantly play sound when page loads */ .edgesIgnoringSafeArea(.all)
//rz- if strand is linear draw line
if dataManager.dataSet?.INSDSeq.topology == "linear" {
Rectangle()
.fill(Color.black)
.frame(width: 500, height: 20)
}
//rz- if strand is circular draw o
if dataManager.dataSet?.INSDSeq.topology == "circular" {
Circle()
.stroke(Color.black, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
.frame(width: 500, height: 500)
}
if dataManager.dataSet?.INSDSeq.topology != "circular" && dataManager.dataSet?.INSDSeq.topology != "linear"{
Text("Data Unavailable, try a new accession number.")
}
}
.navigationBarItems(
//rz - added home and genbank view to navigation bars
leading:
NavigationLink(destination: ContentView()) {
Text("New Query")
.font(.title)
.foregroundColor(Color.black)
},
trailing:
NavigationLink(destination: Screen(seqId: self.$seqId)) {
Text("GenBank View")
.font(.title)
.foregroundColor(Color.black)
Error is in $seqId
NavigationLink(destination: Screen(seqId: self.$seqId)) {
Here is the Screen view
import SwiftUI
struct Screen: View {
#ObservedObject public var dataManager = DataManager.shared
#State private var seqId: String = ""
var body: some View {
VStack {
TextField("Enter Accession Number", text: $seqId)
.padding()
Button("Search")
{
dataManager.downloadSeqData(seqId: seqId)
}
ScrollView {
VStack(alignment: .leading) {
Text(dataManager.dataSet?.INSDSeq.locus ?? "")
.padding()
Text(dataManager.dataSet?.INSDSeq.organism ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.source ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.taxonomy ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.topology ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.length == nil ? "" : "\(dataManager.dataSet!.INSDSeq.length)") .padding()
Text(dataManager.dataSet?.INSDSeq.strandedness ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.moltype ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.sequence ?? "").padding()
}
.cornerRadius(10)
VStack(alignment: .leading, spacing: 10) {
ForEach(dataManager.featureList, id:\.self) { feature in
VStack (alignment: .leading){
VStack (alignment: .leading) {
Text(feature.INSDFeature_key).bold()
Text(feature.INSDFeature_location)
}.padding()
IntervalSection(feature: feature)
QualsSection(feature: feature)
}
.cornerRadius(10)
}
}
.cornerRadius(20)
}
.padding(.horizontal)
}
}
}
struct IntervalSection: View {
var feature: INSDFeature
var body: some View {
VStack (alignment: .leading){
ForEach(0..<feature.INSDFeature_intervals.count, id: \.self) { i in
ForEach(0..<feature.INSDFeature_intervals[i].INSDInterval.count, id: \.self) { j in
if let from = feature.INSDFeature_intervals[i].INSDInterval[j].INSDInterval_from {
VStack (alignment: .leading){
Text("\(from)").bold()
Text("\(feature.INSDFeature_intervals[i].INSDInterval[j].INSDInterval_to ?? -1)")
}
.padding()
.cornerRadius(10)
}
}
}
}
}
}
struct QualsSection: View{
var feature: INSDFeature
var body: some View {
VStack (alignment: .leading){
ForEach(0..<feature.INSDFeature_quals.count , id: \.self) { i in
ForEach(0..<feature.INSDFeature_quals[i].INSDQualifier.count , id: \.self) { j in
VStack (alignment: .leading){
Text(feature.INSDFeature_quals[i].INSDQualifier[j].INSDQualifier_name).bold()
Text(feature.INSDFeature_quals[i].INSDQualifier[j].INSDQualifier_value)
}
.padding()
.cornerRadius(10)
}
}
}
}
}
In SwiftUI you should use Binding instead of State in cases, where the data is coming from another view. Furthermore both attributes of Screen are private, therefore they can't be accessed in this case set from outside of the struct itself.
I would suggest to make seqID in Screen public like you did it with dataManager and change it to State

How to make a button clickable to another view controller only with textfield

I have this scenario that if the user clicks the button then another view controller will popuped which includes only a textView where the user can write something inside.
You can use the the .sheet modifier to display a modal / popup like view in SwiftUI.
struct ContentDetail: View {
struct Item {
let uuid = UUID()
let value: String
}
#State private var items = [Item]()
#State private var show_modal = false
var lectureName:String
var body: some View {
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
self.show_modal.toggle()
}, label: {
Text("✏️")
.font(.system(.largeTitle))
.frame(width: 77, height: 70)
.foregroundColor(Color.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 5,
x: 3,
y: 3)
}
}
}
.sheet(isPresented: self.$show_modal) {
CustomModalView()
}
}
}
struct CustomModalView: View {
#State private var text = ""
var body: some View {
TextField("test", text: $text)
.padding(5)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 60, design: .default))
.multilineTextAlignment(.center)
}
}
You can read more about it here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/