Missing required parameter when just trying to pass "user" in - swiftui

Trying to figure out why its requiring a parameter now when I was just trying to pass in a variable. Had generic text as placeholders for "user.username" and "user.fullname" and when "let user: User" is removed it runs as intended. When added however, the RowView in the previews requires a "user:" parameter.
import SwiftUI
struct RowView: View {
let user: User
var body: some View {
HStack(spacing: 12) {
Circle()
.frame(width: 48, height: 48)
VStack(alignment: .leading, spacing: 4) {
Text(user.username)
.font(.subheadline).bold()
.foregroundColor(.black)
Text(user.fullname)
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
}
.padding(.horizontal)
.padding(.vertical)
}
}
struct RowView_Previews: PreviewProvider{
static var previews: some View {
RowView()
}
}

Declaring user without a starting value indicates that user is a required parameter for the view to be initialized. In the RowView_Previews, which is the preview for the view in the canvas, the RowView is being initialized without the user value. So you essentially have 3 options to fix this:
1. Pass in a user object for the preview
This is probably the best solution. You can pass in a new user object for the preview content:
static var previews: some View {
RowView(user: User())
}
2. Give user a default value
You could give user a default value, meaning that it is no longer a requirement to pass in a user object to initialize RowView:
let user: User = User()
3. Remove the preview provider altogether
If you don't use the previews (which I highly recommend you should), then you could just remove this part altogether, as it is not a requirement for the app to compile:
struct RowView_Previews: PreviewProvider{
static var previews: some View {
RowView()
}
}

you have a variable let user: User so you need to provide the value from anywhere you want to call this class.
See your preview is showing an error. click the error and click fix . then provide User().
or add this code :
struct RowView_Previews: PreviewProvider{
static var previews: some View {
RowView(user: User())
}
}
Or give a default value when you declare the variable . then no need to provide from preview .
like this code:
import SwiftUI
struct RowView: View {
let user: User = User()
var body: some View {
}
}

A usual way to provide default data for the preview is to add a static property to the User struct for example
struct User {
let username, fullname: String
static let example = User(username: "John", fullname: "John Doe")
}
and use it
struct RowView_Previews: PreviewProvider{
static var previews: some View {
RowView(user: User.example)
}
}
By the way
.padding(.horizontal)
.padding(.vertical)
does the same as
.padding()
And it's highly recommended to replace the colors .black with .primary (which is the default) and .gray with .secondary to have an adapted appearance in dark mode.

Related

SwiftUI view get views in the body or define view as property

I am trying to get the view inside the body for observing purpose, but looking for different ways the view inside the body can be accessed.
struct ContentView: View {
#State var userNameText: String
var body: some View {
startObservingInput()
return TextField("hello", text: $userNameText)
}
func startObservingInput() {
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
Option 1 is simple, where we pass the TextField view.
Option 2 is something I am looking for, if we can get any view inside the hierarchy. In this case Text Field.
Option 3, Tried to create a property but I get the following error.
ex:
struct ContentView: View {
#State var userNameText: String
var textField = TextField("hello", text: $userNameText)
......
}
Cannot use instance member '$userNameText' within property initializer; property initializers run before 'self' is available
SwiftUI is different from what you're probably used to. Unlike UIKit, you don't "store" views in properties. There's no delegates either.
Instead, you directly pass in a property — userNameText — that will be linked to the text field's text. Since this updates itself automatically, you can use the onChange modifier to observe changes.
struct ContentView: View {
#State var userNameText: String
var body: some View {
TextField("hello", text: $userNameText)
.onChange(of: userNameText) { newValue in
print("Text changed to: \(newValue)")
}
}
}
Here is what I did, look at it:
//
// ViewProp.swift
// SwiftDemo1
//
// Created by CreoleMacbookPro on 12/19/22.
//
import SwiftUI
struct ViewProp: View {
#State var userNameText: String = " "
var body: some View {
let textField: TextField<Text> = TextField("hello", text: $userNameText)
let simpleText: Text = Text("Hello, World!")
let _ = print(type(of: textField))
startObservingInput(textField: textField)
Button {
userNameText = "Jatin Bhuva"
} label: {
Text("Press Me..")
}
// textField
}
func startObservingInput(textField: TextField<Text>) -> some View {
textField
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
struct ViewProp_Previews: PreviewProvider {
static var previews: some View {
ViewProp()
}
}

Best way to present a long Segmented Picker in Swift UI

I need some suggestions on presenting a segmented picker in Swift UI.
It is to display distinct time ranges (<15min, <30min, <45min) all the way to 120min.
It ends up being 8 segments. I am really not a fan of the scrolling picker as it not in theme what what I am looking for in presentation.
The problem with how it stands now is that the time unit is cut off with each segment showing "15.." and doesn't look clean.
I have put the segmented picker in a horizontal scroll view which looks okay but the user may not know to scroll.
One option I used but can't get to work out is splitting the one long segment into 2 separate views.
The problem is the user can select a segment from either pickers which is not what I want.
What I want is if the user selects one picker, the other one is not selectable or vice versa.
I have been messing with some formatting options, so please ignore that.
Is this possible?
Thanks is advance!
struct ContentView: View {
var body: some View {
VStack{
To60min()
To120min()
.foregroundColor(Color.red)
}
}}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}}
struct To60min: View {
#State private var selectedTimeRangeto60 = ""
#State private var timeRangesTo60 = ["15min", "30min", "45min", "60min"]
var body: some View {
Picker("", selection: $selectedTimeRangeto60) {
ForEach(timeRangesTo60, id: \.self) {
Text($0)
}
}
.frame(width: .infinity, height: 75)
.background(.gray)
.padding()
.pickerStyle(.segmented)
.contrast(22.0)
}
}
struct To120min: View {
#State private var selectedTimeRangeto120 = ""
#State private var timeRangesTo120 = ["75min", "90min", "105min", "120min"]
var body: some View {
Picker("", selection: $selectedTimeRangeto120) {
ForEach(timeRangesTo120, id: \.self) {
Text($0)
}
}
.padding()
.pickerStyle(.segmented)
.contrast(22)
}
}
For anything more than 3-4 items (depending on label length), I would switch from a .segmented to .menu picker style. https://developer.apple.com/documentation/swiftui/pickerstyle

SwiftUI clean up ContentView

I'm trying to simplify the ContentView within a project and I'm struggling to understand how to move #State based logic into its own file and have ContentView adapt to any changes. Currently I have dynamic views that display themselves based on #Binding actions which I'm passing the $binding down the view hierarchy to have buttons toggle the bool values.
Here's my current attempt. I'm not sure how in SwiftUI to change the view state of SheetPresenter from a nested view without passing the $binding all the way down the view stack. Ideally I'd like it to look like ContentView.overlay(sheetPresenter($isOpen, $present).
Also, I'm learning SwiftUI so if this isn't the best approach please provide guidance.
class SheetPresenter: ObservableObject {
#Published var present: Present = .none
#State var isOpen: Bool = false
enum Present {
case none, login, register
}
#ViewBuilder
func makeView(with presenter: Present) -> some View {
switch presenter {
case .none:
EmptyView()
case .login:
BottomSheetView(isOpen: $isOpen, maxHeight: UIConfig.Utils.screenHeight * 0.75) {
LoginScreen()
}
case .register:
BottomSheetView(isOpen: $isOpen, maxHeight: UIConfig.Utils.screenHeight * 0.75) {
RegisterScreen()
}
}
}
}
if you don't want to pass $binding all the way down the view you can create a StateObject variable in the top view and pass it with .environmentObject(). and access it from any view with EnvironmentObject
struct testApp: App {
#StateObject var s1: sViewModel = sViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(s1)
}
}
}
You are correct this is not the best approach, however it is a common mistake. In SwiftUI we actually use #State for transient data owned by the view. This means using a value type like a struct, not classes. This is explained at 4:18 in Data Essentials in SwiftUI from WWDC 2020.
EditorConfig can maintain invariants on its properties and be tested
independently. And because EditorConfig is a value type, any change to
a property of EditorConfig, like its progress, is visible as a change
to EditorConfig itself.
struct EditorConfig {
var isEditorPresented = false
var note = ""
var progress: Double = 0
mutating func present(initialProgress: Double) {
progress = initialProgress
note = ""
isEditorPresented = true
}
}
struct BookView: View {
#State private var editorConfig = EditorConfig()
func presentEditor() { editorConfig.present(…) }
var body: some View {
…
Button(action: presentEditor) { … }
…
}
}
Then you just use $editorConfig.isEditorPresented as the boolean binding in .sheet or .overlay.
Worth also taking a look at sheet(item:onDismiss:content:) which makes it much simpler to show an item because no boolean is required it uses an optional #State which you can set to nil to dismiss.

How to solve this problem of "environment object"?

//environment object class
class AppData: ObservableObject {
#Published var studs : [StudentModel]
}
var body: some View {
VStack{
List(appData.studs,id:\.rollNo){ s in //causing error
Text("\(s.rollNo)")
NavigationLink("", destination: StudentView(s: s))
}
}.navigationBarItems(trailing:
Button(action: {
self.addStud.toggle()
}){
Image(systemName: "plus")
.renderingMode(.original)
}
.sheet(isPresented: $addStud, content: {
AddStudent()
})
)
.navigationBarTitle(Text("Students"),displayMode: .inline)
}
Fatal error: No ObservableObject of type AppData found. A View.environmentObject(_:) for AppData may be missing as an ancestor of this view.
Your sample code is missing some lines at the start of the view. By the sounds of the error message, you already have something like:
struct MyView: View {
#EnvironmentObject var appData: AppData
// ...rest of view ...
}
Alongside that code to get a reference for your object out of the environment, you also need to ensure that, somewhere further up the chain, it's put in. Your error message is telling you that that is where the problem lies – it's looking in the environment for a type of AppData object, but there's nothing in there.
Let's say you declare it the app level; it might look something like this:
#main
struct TestDemoApp: App {
// 1. Instantiate the object, using `#StateObejct` to make sure it's "owned" by the view
#StateObject var appData = AppData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appData) // 2. make it available to the hierarchy of views
}
}
}
What you'll also have to do is make sure that any views that use your environment object also have access to one in their Xcode previews. You might want to create a version of AppData that has example data inside so that your previews don't mess with live data.
extension AppData {
static var preview: AppData = ...
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(AppData.preview)
}
}

Set initial value for a TextField in SwiftUI - compare new and old value

I have seen a lot of examples and tutorial of how to use an empty TextField for collecting new values, but none that shows how to use a TextField to edit a value.
In my use-case, I want the TextField to be prepopulated/prefilled with data from my viewmodel, then as user edits the data, a Save button should be enabled. In my form, I also have a navigationlink that leads to a sub-page where the user can select something from a list, and then be routed back to the form.
It behaves as described as long I use an empty field; the user can type something temporary in the field, navigate to the sub page, and the temp value is still like it was when he left.
struct TextFieldDemo: View {
var model:String // Actual a more complex view model
#State var editedValue:String = ""
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Group{
Text("Some label")
TextField("Placeholder text", text: $editedValue)
}
Divider()
Text("Some navigation link to push in a page where " +
"the user can select something from a list and click back...")
// If the user starts to edit the textfield - follows a navigation link and comes back
// he should be able to continue edit the field where he left of - the text field should
// not have been reset to the original value.
Button(action: {
// Call some save function in the ViewModel
},label: {
Text("SAVE")
}
).disabled(model == editedValue)
}.onAppear(){
// I could have done something like:
// self.editedValue = model
// but it seems like this will fire if the user navigates into the described page and reset
// the TextField to the model value.
}
}
}
struct TextFieldDemo_Previews: PreviewProvider {
static var previews: some View {
TextFieldDemo(model: "The old value")
}
}
To initialize the text field with the value from your model, you need to define your own initializer and use the State(wrappedValue:) initializer for #State vars:
struct TextFieldDemo: View {
var model:String // Actual a more complex view model
#State var editedValue: String
init(model: String) {
self.model = model
self._editedValue = State(wrappedValue: model) // _editedValue is State<String>
}
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Group{
Text("Some label")
TextField("Placeholder text", text: $editedValue)
}
Divider()
Text("Some navigation link to push in a page where " +
"the user can select something from a list and click back...")
// If the user starts to edit the textfield - follows a navigation link and comes back
// he should be able to continue edit the field where he left of - the text field should
// not have been reset to the original value.
Button(action: {
// Call some save function in the ViewModel
},label: {
Text("SAVE")
}
).disabled(model == editedValue)
}.onAppear(){
// I could have done something like:
// self.editedValue = model
// but it seems like this will fire if the user navigates into the described page and reset
// the TextField to the model value.
}
}
}
struct TextFieldDemo_Previews: PreviewProvider {
static var previews: some View {
TextFieldDemo(model: "The old value")
}
}
how about something like this test code. The key is to use the "ObservableObject":
import SwiftUI
class MyModel: ObservableObject {
#Published var model = "model1"
}
struct ContentView: View {
#ObservedObject var myModel = MyModel()
#State var editedValue = ""
var body: some View {
NavigationView {
VStack(alignment: .leading, spacing: 20) {
Group{
Text("Some label")
TextField("Placeholder text", text: Binding<String>(
get: { self.editedValue },
set: {
self.editedValue = $0
self.myModel.model = self.editedValue
})).onAppear(perform: loadData)
}
Divider()
NavigationLink(destination: Text("the nex page")) {
Text("Click Me To Display The next View")
}
// If the user starts to edit the textfield - follows a navigation link and comes back
// he should be able to continue edit the field where he left of - the text field should
// not have been reset to the original value.
Button(action: {
// Call some save function in the ViewModel
self.myModel.model = self.editedValue
},label: {
Text("SAVE")
})
}
}.navigationViewStyle(StackNavigationViewStyle())
}
func loadData() {
self.editedValue = myModel.model
}
}