Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing state variable in SwiftUI View outside the body

I have a content view and I want to track dynamically the height.

I have implemented this solution but i encounter this Exception :

Fatal error: Accessing State outside View.body: file

What can i do to have keyboardHeight dynamic which can update my UI

struct ContentView: View {

    @State var keyboardHeight: CGFloat = 0
    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: self)
        .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: keyboardHeight)
        }
    }
}
like image 806
Kevin ABRIOUX Avatar asked Mar 04 '23 11:03

Kevin ABRIOUX


1 Answers

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)
        }
    }
}
like image 56
kontiki Avatar answered May 14 '23 11:05

kontiki