I'm trying to get from UserDefaults a Bool whose key depends on the nameID of a Product.
I tried this way:
import SwiftUI
struct ProductDetail: View {
var product: GumroadProduct
#AppStorage("LOCAL_LIBRARY_PRESENCE_PRODUCTID_\(product.id)") var isLocal: Bool = false
var body: some View {
Text("ProductView")
}
}
Anyway Swift throws this error:
Cannot use instance member 'product' within property initializer;
property initializers run before 'self' is available
I understand why Swift is throwing that error but I don't know how to get around it.
Is there a solution?
Here is a solution for your code snapshot - provide explicit initialiser and instantiate properties in it depending on input:
struct ProductDetail: View {
#AppStorage private var isLocal: Bool
private var product: GumroadProduct
init(product: GumroadProduct) {
self.product = product
self._isLocal = AppStorage(wrappedValue: false, "LOCAL_LIBRARY_PRESENCE_PRODUCTID_\(product.id)")
}
var body: some View {
Text("ProductView")
}
}
Related
I'm working on a Swift package where an option will be to pass in a generic type (Person) and then that GenericStruct can use properties on that type passed in. The issue obviously is that the generic T has no idea what's being passed in. Is there a way to define the property to access on the generic type T?
struct Person: Equatable {
var name: String
var height: Double
}
struct ContentView: View {
#State private var james = Person(name: "James", height: 175.0)
var body: some View {
GenericStruct(person: $james)
}
}
struct GenericStruct<T: Equatable>: View {
#Binding var person: T
var body: some View {
Text(person.name)) // This line.
}
}
I want to specifically pass in which property to access on Person when passing it to GenericStruct. The property won't always be name it could be anything I define within Person. For example:
GenericStruct(person: $james, use: Person.name)
Isn't this exactly a protocol?
protocol NameProviding {
var name: String { get }
}
struct GenericStruct<T: Equatable & NameProviding>: View { ... }
Or is there a more subtle part of the question?
The best way to do this is to pass a String Binding:
struct GenericStruct: View {
#Binding var text: String
var body: some View {
Text(text)) // This line.
}
}
GenericStruct(text: $james.name)
But it is possible with key paths. It's just a bit more awkward and less flexible in this particular case:
// Should be the syntax, but I haven't double-checked it.
struct GenericStruct<T: Equatable>: View {
#Binding var person: T
var use: KeyPath<T, String>
var body: some View {
Text(person[keyPath: use]))
}
}
GenericStruct(person: $james, use: \.name)
I apologize I am new to Swift and may be going about this completely wrong.
I am attempting to call the mutating function on my struct in my view to add additional phones or emails. This is my Struct.
struct CreateCustomer: Codable {
var Phone: [CustomerPhone]
var Emails: [String]
init() {
Phone = [CustomerPhone()]
Emails = []
}
public mutating func addPhone(){
Phone.append(CustomerPhone())
}
public mutating func addEmail(){
Emails.append("")
}
}
struct CustomerPhone: Codable {
var Phone: String
var PhoneType: Int
init(){
Phone = ""
PhoneType = 0
}
}
I am attempting to add a phone to my state var with the following
Button("Add Phone"){
$Customer_Create.addPhone()
}
I get the following Error
Cannot call value of non-function type 'Binding<() -> ()>' Dynamic key
path member lookup cannot refer to instance method 'addPhone()'
Thank you for any help!
If Customer_Create is a state property variable (like below) then you don't need binding, use property directly
struct ContentView: View {
#State private var Customer_Create = CreateCustomer()
var body: some View {
Button("Add Phone"){
Customer_Create.addPhone() // << here !!
}
}
}
You shouldn't be accessing the Binding via $, you should simply access the property itself.
Button("Add Phone"){
Customer_Create.addPhone()
}
Unrelated to your question, but you should be conforming to the Swift naming convention, which is lowerCamelCase for variables and properties - so customerCreate, not Customer_Create.
Can #StateObject be injected using Resolver?
I have the following:
struct FooView: View {
#StateObject private var viewModel: FooViewModel
some code
}
protocol FooViewModel: ObservableObject {
var someValue: String { get }
func someRequest()
}
class FooViewModelImpl {
some code
}
I would like to inject FooViewModel into FooView using Resolver but have been struggling as Resolver wants to use the #Inject annotation and of course, I need the #StateObject annotation but I cannot seem to use both. Are #StateObject not able to be injected using some Dependency Injection framework like Resolver? I have not found any examples where developers have used DI in this approach.
The latest version of Resolver supports #InjectedObject property wrapper for ObservableObjects. This wrapper is meant for use in SwiftUI Views and exposes bindable objects similar to that of SwiftUI #ObservedObject and #EnvironmentObject.
I am using it a lot now and its very cool feature.
eg:
class AuthService: ObservableObject {
#Published var isValidated = false
}
class LoginViewModel: ObservableObject {
#InjectedObject var authService: AuthService
}
Note: Dependent service must be of type ObservableObject. Updating object state will trigger view update.
If your StateObject has a dependency - and instead to utilise a heavy weight Dependency Injection Framework - you could utilise Swift Environment and a super light wight "Reader Monad" to setup your dependency injected state object, and basically achieve the same, just with a few lines of code.
The following approach avoids the "hack" to setup a StateObject within the body function, which may lead to unexpected behaviour of the StateObject. The dependent object will be fully initialised once and only once with a default initialiser, when the view will be created. The dependency injection happens later, when a function of the dependent object will be used:
Given a concrete dependency, say SecureStore conforming to a Protocol, say SecureStorage:
extension SecureStore: SecureStorage {}
Define the Environment Key and setup the default concrete "SecureStore":
private struct SecureStoreKey: EnvironmentKey {
static let defaultValue: SecureStorage =
SecureStore(
accessGroup: "myAccessGroup"
accessible: .whenPasscodeSetThisDeviceOnly
)
}
extension EnvironmentValues {
var secureStore: SecureStorage {
get { self[SecureStoreKey.self] }
set { self[SecureStoreKey.self] = newValue }
}
}
Elsewhere, you have a view showing some credential from the secure store, which access will be handled by the view model, which is setup as a #StateObject:
struct CredentialView: View {
#Environment(\.secureStore) private var secureStore: SecureStorage
#StateObject private var viewModel = CredentialViewModel()
#State private var username: String = "test"
#State private var password: String = "test"
var body: some View {
Form {
Section(header: Text("Credentials")) {
TextField("Username", text: $username)
.keyboardType(.default)
.autocapitalization(.none)
.disableAutocorrection(true)
SecureField("Password", text: $password)
}
Section {
Button(action: {
self.viewModel.send(.submit(
username: username,
password: password
))
.apply(e: secureStore)
}, label: {
Text("Submitt")
.frame(minWidth: 0, maxWidth: .infinity)
})
}
}
.onAppear {
self.viewModel.send(.readCredential)
.apply(e: secureStore)
}
.onReceive(self.viewModel.$viewState) { viewState in
print("onChange: new: \(viewState.credential)")
username = viewState.credential.username
password = viewState.credential.password
}
}
}
The interesting part here is where and when to perform the dependency injection:
self.viewModel.send(.submit(...))
.apply(e: secureStore) // apply the dependency
Here, the dependency "secureStore" will be injected into the view model in the action function of the Button within the body function, utilising the a "Reader", aka .apply(environment: <dependency>).
Note also that the ViewModel provides a function
send(_ Event:) -> Reader<SecureStorage, Void>
where Event just is an Enum which has cases for every possible User Intent.
final class CredentialViewModel: ObservableObject {
struct ViewState: Equatable {
var credential: Credential =
.init(username: "", password: "")
}
enum Event {
case submit(username: String, password: String)
case readCredential
case deleteCredential
case confirmAlert
}
#Published var viewState: ViewState = .init()
func send(_ event: Event) -> Reader<SecureStorage, Void>
...
Your View Model can then implement the send(_:) function as follows:
func send(_ event: Event) -> Reader<SecureStorage, Void> {
Reader { secureStore in
switch event {
case .readCredential:
...
case .submit(let username, let password):
secureStore.set(
item: Credential(
username: username,
password: password
),
key: "credential"
)
case .deleteCredential:
...
}
}
Note how the "Reader" will be setup. Basically quite easy:
A Reader just holds a function: (E) -> A, where E is the dependency and A the result of the function (here Void).
The Reader pattern may be mind boggling at first. However, just think of send(_:) returns a function (E) -> Void where E is the secure store dependency, and the function then just doing whatever was needed to do when having the dependency. In fact, the "poor man" reader would just return this function, just not a "Monad". Being a Monad opens the opportunity to compose the Reader in various cool ways.
Minimal Reader Monad:
struct Reader<E, A> {
let g: (E) -> A
init(g: #escaping (E) -> A) {
self.g = g
}
func apply(e: E) -> A {
return g(e)
}
func map<B>(f: #escaping (A) -> B) -> Reader<E, B> {
return Reader<E, B>{ e in f(self.g(e)) }
}
func flatMap<B>(f: #escaping (A) -> Reader<E, B>) -> Reader<E, B> {
return Reader<E, B>{ e in f(self.g(e)).g(e) }
}
}
For further information about the Reader Monad:
https://medium.com/#foolonhill/techniques-for-a-functional-dependency-injection-in-swift-b9a6143634ab
Not sure about resolver but you can pass VM to a V using the following approach.
import SwiftUI
class FooViewModel: ObservableObject {
#Published var counter: Int = 0
}
struct FooView: View {
#StateObject var vm: FooViewModel
var body: some View {
VStack {
Button {
vm.counter += 1
} label: {
Text("Increment")
}
}
}
}
struct ContentView: View {
var body: some View {
FooView(vm: FooViewModel())
}
}
No, #StateObject is for a separate source of truth it shouldn't have any other dependency. To pass in an object, e.g. the object that manages the lifetime of the model structs, you can use #ObservedObject or #EnvironmentObject.
You can group your related vars into their own custom struct and use mutating funcs to manipulate them. You can even use #Environment vars if you conform the struct to DynamicProperty and read them in the update func which is called on the struct before the View's body. You can even use a #StateObject if you need a reference type, e.g. to use as an NSObject's delegate.
FYI we don't use view models objects in SwiftUI. See this answer "MVVM has no place in SwiftUI."
ObservableObject is part of the Combine framework so you usually only use it when you want to assign the output of a Combine pipeline to an #Published property. Most of the time in SwiftUI and Swift you should be using value types like structs. See Choosing Between Structures and Classes. We use DynamicProperty and property wrappers like #State and #Binding to make our structs behave like objects.
I'm trying to remove the logic from the view, while keeping the benefits of SwiftUI. Idea 1 works but it makes use of an extra variable than I would want to. Idea 2 gives error: Property wrappers are not yet supported on local properties. The view should return "bar". What is the best way of making this work? Many thanks.
import Combine
import Foundation
import SwiftUI
// Model
enum Model: String, RawRepresentable {
case foo = "foo"
case bar = "bar"
}
// State
var data1: String = Model.foo.rawValue
class State: ObservableObject {
#Published internal var data2: String = data1
}
// Logic
func logic() {
// Idea 1: OK
//data1 = Model.bar.rawValue
//print(State().data2)
// Idea 2: Error Property wrappers are not yet supported on local properties
#EnvironmentObject private var state: State
state.data2 = Model.bar.rawValue
print(state.data2)
}
// View
struct bar: View {
#EnvironmentObject private var state: State
internal var body: some View {
logic()
return Text(verbatim: self.state.data2)
}
}
If you want a function to have access to a view's state, pass the state:
func logic(state: State) {
state.data2 = Model.bar.rawValue
print(state.data2)
}
But what you've done here is an infinite loop. Modifying a view's state causes the view to be re-rendered. So every time the view is rendered, it modifies its state and forces it to be rendered again. That will never resolve. What you may mean here is to change the state when the view first appears, in which case you'd call logic this way:
struct Bar: View {
#EnvironmentObject private var state: State
internal var body: some View {
Text(verbatim: state.data2)
.onAppear{ logic(state: self.state) }
}
}
Using Xcode 11 beta 6, I am trying to declare a protocol for a type with properties using #Published (but this question can be generalized to any PropertyWrapper I guess).
final class DefaultWelcomeViewModel: WelcomeViewModel & ObservableObject {
#Published var hasAgreedToTermsAndConditions = false
}
For which I try to declare:
protocol WelcomeViewModel {
#Published var hasAgreedToTermsAndConditions: Bool { get }
}
Which results in a compilation error: Property 'hasAgreedToTermsAndConditions' declared inside a protocol cannot have a wrapper
So I try to change it into:
protocol WelcomeViewModel {
var hasAgreedToTermsAndConditions: Published<Bool> { get }
}
And trying
Which does not compile, DefaultWelcomeViewModel does not conform to protocol, okay, so hmm, I cannot using Published<Bool> then, let's try it!
struct WelcomeScreen<ViewModel> where ViewModel: WelcomeViewModel & ObservableObject {
#EnvironmentObject private var viewModel: ViewModel
var body: some View {
// Compilation error: `Cannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'`
Toggle(isOn: viewModel.hasAgreedToTermsAndConditions) {
Text("I agree to the terms and conditions")
}
}
}
// MARK: - ViewModel
protocol WelcomeViewModel {
var hasAgreedToTermsAndConditions: Published<Bool> { get }
}
final class DefaultWelcomeViewModel: WelcomeViewModel & ObservableObject {
var hasAgreedToTermsAndConditions = Published<Bool>(initialValue: false)
}
Which results in the compilation error on the Toggle: Cannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'.
Question: How can I make a protocol property for properties in concrete types using PropertyWrappers?
I think the explicit question you're asking is different from the problem you are trying to solve, but I'll try to help with both.
First, you've already realized you cannot declare a property wrapper inside a protocol. This is because property wrapper declarations get synthesized into three separate properties at compile-time, and this would not be appropriate for an abstract type.
So to answer your question, you cannot explicitly declare a property wrapper inside of a protocol, but you can create individual property requirements for each of the synthesized properties of a property wrapper, for example:
protocol WelcomeViewModel {
var hasAgreed: Bool { get }
var hasAgreedPublished: Published<Bool> { get }
var hasAgreedPublisher: Published<Bool>.Publisher { get }
}
final class DefaultWelcomeViewModel: ObservableObject, WelcomeViewModel {
#Published var hasAgreed: Bool = false
var hasAgreedPublished: Published<Bool> { _hasAgreed }
var hasAgreedPublisher: Published<Bool>.Publisher { $hasAgreed }
}
As you can see, two properties (_hasAgreed and $hasAgreed) have been synthesized by the property wrapper on the concrete type, and we can simply return these from computed properties required by our protocol.
Now I believe we have a different problem entirely with our Toggle which the compiler is happily alerting us to:
Cannot convert value of type 'Published' to expected argument type 'Binding'
This error is straightforward as well. Toggle expects a Binding<Bool>, but we are trying to provide a Published<Bool> which is not the same type. Fortunately, we have chosen to use an #EnvironmentObject, and this enables us to use the "projected value" on our viewModel to obtain a Binding to a property of the view model. These values are accessed using the $ prefix on an eligible property wrapper. Indeed, we have already done this above with the hasAgreedPublisher property.
So let's update our Toggle to use a Binding:
struct WelcomeView: View {
#EnvironmentObject var viewModel: DefaultWelcomeViewModel
var body: some View {
Toggle(isOn: $viewModel.hasAgreed) {
Text("I agree to the terms and conditions")
}
}
}
By prefixing viewModel with $, we get access to an object that supports "dynamic member lookup" on our view model in order to obtain a Binding to a member of the view model.
I would consider another solution - have the protocol define a class property with all your state information:
protocol WelcomeViewModel {
var state: WelcomeState { get }
}
And the WelcomeState has the #Published property:
class WelcomeState: ObservableObject {
#Published var hasAgreedToTermsAndConditions = false
}
Now you can still publish changes from within the viewmodel implementation, but you can directly observe it from the view:
struct WelcomeView: View {
#ObservedObject var welcomeState: WelcomeState
//....
}