Accessing ViewModel field in SwiftUI using Xcode 12: Accessing State's value outside of being installed on a View
Thanks to @Andrew's answer I figured out how to make it work again. First you change the @State
to @Published
:
@Published public var amount: Int = 1
Next, you need to change how your Picker
is bound to the data:
Picker(selection: $model.amount, label: Text("Amount")) {
Text("€1").tag(1)
Text("€2").tag(2)
Text("€5").tag(5)
Text("€10").tag(10)
}.pickerStyle(SegmentedPickerStyle())
So we went from model.$amount
to $model.amount
.
SwiftUI binding bool outside view - 'Accessing State's value outside of being installed on a View'
You can try having all the logic in the ViewModel
:
class ViewModel: ObservableObject {
@Published var currentQuestion: String!
@Published var showResults = false
private var questions = ["one", "two", "three"]
private var index = 0
init() {
currentQuestion = questions[index]
}
func nextQuestion() {
if index < questions.count {
index += 1
currentQuestion = questions[index]
} else {
showResults = true
}
}
}
and UI components (eg. buttons) in the View:
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
VStack {
Text(viewModel.currentQuestion)
Button(action: viewModel.nextQuestion) {
Text("Next")
}
}
// if viewModel.showResults == true -> navigate to results
.background(NavigationLink(destination: ResultsView(), isActive: $viewModel.showResults) { EmptyView() } .hidden())
}
}
}
In the above example you go to the ResultsView
using a NavigationLink
but you can easily replace the current view with results. The presentation is up to you.
It is possible to accessing FocusState's value outside of the body of a View
It is in-view wrapper (same as State
). But it is possible to map it to published property like in below approach.
Tested with Xcode 13.2 / iOS 15.2
struct ContentView: View {
@ObservedObject private var viewModel = ViewModel()
@FocusState var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
.onChange(of: viewModel.hasFocus) {
hasFocus = $0
}
.onChange(of: hasFocus) {
viewModel.hasFocus = $0
}
Button("Set Focus") {
viewModel.hasFocus = true
}
}
}
}
class ViewModel: ObservableObject {
@Published var textField: String = ""
@Published var hasFocus: Bool = false
}
Accessing StateObject's object without being installed on a View. This will create a new instance each time - SwiftUI
You cannot access any value before they get initialized, use onAppear()
:
import SwiftUI
@main
struct YourApp: App {
@StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()
var body: some Scene {
WindowGroup {
ContentView()
.onAppear() {
if (!amplifyConfig.isAmplifyConfigured) {
amplifyConfig.dataStoreHubEventSubscriber()
amplifyConfig.configureAmplify()
}
}
}
}
}
Update: An actual use case
import SwiftUI
@main
struct YourApp: App {
@StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()
@State private var isLoaded: Bool = Bool()
var body: some Scene {
WindowGroup {
VStack {
if (isLoaded) { ContentView() }
else { Text("Loading . . .") }
}
.onAppear() {
if (!amplifyConfig.isAmplifyConfigured) {
amplifyConfig.dataStoreHubEventSubscriber()
amplifyConfig.configureAmplify()
completionHandler { value in isLoaded = value }
}
else {
isLoaded = true
}
}
}
}
}
func completionHandler(value: @escaping (Bool) -> Void) {
// Some heavy work here, I am using DispatchQueue.main.asyncAfter for replicating that heavy work is happening! But you use your own code here.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(3000)) { value(true) }
}
Accessing state variable in SwiftUI View outside the body
The error message is pretty much saying exactly what the problem is. However, although you cannot access State variables outside the body function (or a function called from body), you can access observable objects. So simply changing @State by @ObservedObject and defining a tiny class, is enough to make it work.
Note that this solution is to keep your workflow idea intact. Although, there're other ways to accomplish the same thing.
import SwiftUI
import Combine
class Model: ObservableObject {
@Published var keyboardHeight: CGFloat = 0
}
struct ContentView: View {
@ObservedObject var model = Model()
var cancellables: Set<AnyCancellable> = []
init() {
NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)
.merge(with: NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification))
.compactMap({ notification in
guard let keyboardFrameValue: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return nil }
let keyboardFrame = keyboardFrameValue.cgRectValue
if keyboardFrame.origin.y == UIScreen.main.bounds.height {
return 0
} else {
return keyboardFrame.height - (UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0)
}
})
.assign(to: \.keyboardHeight, on: model)
.store(in: &cancellables)
}
var body: some View {
VStack{
ZStack(alignment: Alignment.bottom) {
List {
Text("Default text").foregroundColor(Color.red)
}
TextField("Placeholder", text: .constant(""))
.frame(minHeight: 30)
.cornerRadius(8.0)
.padding(10)
.background(Color.blue)
}
Spacer()
.frame(height: model.keyboardHeight)
}
}
}
How to assign value to @State in View from ViewModel?
you could try this approach, using .onReceive(...)
. Add this to yourZStack
or NavigationView
:
.onReceive(Just(viewModel.moreRemaining)) { val in
reachedLastPage = !val
}
Also add: import Combine
SwiftUI: Change view @State property from ViewModel
You can directly bind to the @Published
property with just:
$viewModel.startTime
Which will be of type Binding<Date>
, even though we declare it as just a @Published
property on BookTimeViewModel
.
Example code:
class BookTimeViewModel: ObservableObject {
@Published var startTime: Date = .now
}
struct BookTimeView: View {
@ObservedObject var viewModel: BookTimeViewModel
var body: some View {
DatePicker("pick time", selection: $viewModel.startTime, displayedComponents: .hourAndMinute)
}
}
Similar to my answer here.
Related Topics
How to Write an 'If Case' Statement as an Expression
How Get Rootviewcontroller with iPados Multi Window (Scenedelegate)
How to Save Cgimage to Data in Swift
Swift3:How to Handle Precedencegroup Now Operator Should Be Declare with a Body
How to Fix ' *Pod* Does Not Support Provisioning Profiles' in Azure Devops Build Agent
Creating a Sliding Segue in Swift
How to Install Swift Package via Package Manager
Create Instance of Class Known at Runtime in Swift
How to Simulate Traits/Mixins in Swift
Reducing the Number of Brackets in Swift
Avoid Consecutive "If Let" Declarations in Swift
How to Test That Statictexts Contains a String Using Xctest
Convincing Swift That a Function Will Never Return, Due to a Thrown Exception