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 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")
}
}
I would like to declare a Swift protocol that requires conforming classes define a #Published Bool
class MyModel: ObservableObject, MyProtocol {
#Published var myBool: Bool = false
.
.
}
My best shot at this is,
protocol MyProtocol {
var _myBool: Binding<Bool> { get set }
}
...but the compiler complains, "Cannot find type 'Binding' in scope"
In my case, the class implementing the protocol will define the Bool, so I don't want a shared implementation (extension), I just need to require the class to have its own.
How can I achieve this?