4

I know that State wrappers are for View and they designed for this goal, but I wanted to try build and test some code if it is possible, my goal is just for learning purpose,

I have 2 big issues with my code!

  1. Xcode is unable to find T.

  2. How can I initialize my state?

import SwiftUI

var state: State<T> where T: StringProtocol = State(get: { state }, set: { newValue in state = newValue })

struct ContentView: View {
    var body: some View {
        Text(state)
    }
}

Update: I could do samething for Binding here, Now I want do it for State as well with up code

import SwiftUI

var state2: String = String() { didSet { print(state2) } }
var binding: Binding = Binding.init(get: { state2 }, set: { newValue in state2 = newValue })


struct ContentView: View {

    var body: some View {
        
        TextField("Enter your text", text: binding)

    }
 
}

If I could find the answer of my issue then, i can define my State and Binding both outside of View, 50% of this work done and it need another 50% for State Wrapper.


New Update:

import SwiftUI

var state: State<String> = State.init(initialValue: "Hello") { didSet { print(state.wrappedValue) } }
var binding: Binding = Binding.init(get: { state.wrappedValue }, set: { newValue in state = State(wrappedValue: newValue) })


struct ContentView: View {

    var body: some View {
        
        Text(state)                                     // <<: Here is the issue!
        
        TextField("Enter your text", text: binding)

    }
 
}

enter image description here

10
  • 1
    @aheze: the reason that I want done it in State wrapper way is because I did the same thing for Binding and it just worked! because I could make and define a Binding outside View, I thought why not State, look my update code. plz. in up Commented Mar 10, 2021 at 16:26
  • 1
    A generic outside of view? how will you satisfy generic type T ? Currently your T is constrained to stringProtoco l, but how will you satisfy concrete type for T? T doesn’t know whom to communicate with. Also you are passing State<String> in Text, which is incorrect. Another thing initialiser for State doesn’t have get and set @escaping blocks, unlike Bindings. Commented Mar 10, 2021 at 17:04
  • 1
    @TusharSharma: yes, you are right, that is why asked for help :) Commented Mar 10, 2021 at 17:10
  • 1
    you can have normal State object outside view like var state = State(wrappedValue: “foo”), and you can use projectedValue as Binding inside views, and wrapped value to access actual value. That’s one way without going generic. Is generic a must requirement? Commented Mar 10, 2021 at 17:17
  • 1
    with what I recommended above this is what is got at runtime -: Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update. So, I was wrong don’t use it. Commented Mar 10, 2021 at 17:21

1 Answer 1

4

Even if you create a State wrapper outside a view, how will the view know when to refresh its body?

Without a way to notify the view, your code will do the same as:

struct ContentView: View {
    var body: some View {
        Text("Hello")
    }
}

What you can do next depends on what you want to achieve.

If all you need is a way to replicate the State behaviour outside the view, I recommend you take a closer look at the Combine framework.

An interesting example is CurrentValueSubject:

var state = CurrentValueSubject<String, Never>("state1")

It stores the current value and also acts as a Publisher.

What will happen if we use it in a view that doesn't observe anything?

struct ContentView: View {
    var body: some View {
        Text(state.value)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    state.value = "state2"
                }
            }
    }
}

The answer is: nothing. The view is drawn once and, even if the state changes, the view won't be re-drawn.

You need a way to notify the view about the changes. In theory you could do something like:

var state = CurrentValueSubject<String, Never>("state1")
struct ContentView: View {
    @State var internalState = ""

    var body: some View {
        Text(internalState)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    state.value = "state2"
                }
            }
            .onReceive(state) {
                internalState = $0
            }
    }
}

But this is neither elegant nor clean. In these cases we should probably use @State:

struct ContentView: View {
    @State var state = "state1"

    var body: some View {
        Text(state)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    state = "state2"
                }
            }
    }
}

To sum up, if you need a view to be refreshed, just use the native SwiftUI property wrappers (like @State). And if you need to declare state values outside the view, use ObservableObject + @Published.

Otherwise there is a huge Combine framework which does exactly what you want. I recommend you take a look at these links:

Sign up to request clarification or add additional context in comments.

6 Comments

To be honest, I think my way is the way Apple use for State, but Apple hide some code from us, and that is the code that invoke the View to use ready updated value for State, you say how? I would say, After that State got updated, State itself send some code to View and Says: Hey! I got updated! So update yourself as well! The code that apple is hiding is connected to location of State. we have no access to this location parameter! your way is understandable and I am thankful for your help but my idea was using State way not class or combine. voted
@swiftPunk If you're interested in how SwiftUI views know they should be refreshed when @State changes, take a look at DynamicProperty. Also, in SwiftUI you'd never use var state: State at the global level (outside structs etc). If you want to create your own property wrapper, create it in a place where it belongs to - not at the root level. This might be helpful: hackingwithswift.com/plus/intermediate-swiftui/…
@swiftPunk Also, to take advantage of DynamicProperty you need to use a property wrapper (with the @ symbol). You can only do it inside a struct/class/etc. Xcode clearly states that Property wrappers are not yet supported in top-level code.
thanks for all information, I would read and study them. You said i should create State in its place not outside! But I already explained at begging of my question that I know all of those fact and i am trying test and experiment things.
@swiftPunk What I'm trying to say is that the Swift language doesn't currently allow using property wrappers at the global level. So it can't be done. Remember that var state: State is not a property wrapper but just a simple struct. Only @State var state is a property wrapper (with @).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.