How to check if #EnvironmentObject is set? (SwiftUI) - crash

What I have: View and ViewModel (as extension to View struct).
Both of them are use #EnvironmentObject of type AppState.
The problem is that my preview crashes due to this error:
Fatal error: No ObservableObject of type AppState found.
Commenting out lines in loadUser func saves from crash.
struct ProfileView: View {
#EnvironmentObject var appState: AppState
#ObservedObject var viewModel = ViewModel()
...
}
extension ProfileView {
class ViewModel: ObservableObject {
#EnvironmentObject var appState: AppState
#Published var userVM = UserVM(.example)
init() {
loadUser()
}
func loadUser() {
User.WebService.getSelf { user, errorMsg in
DispatchQueue.main.async {
guard let user = user else {
/*self.appState.showingAlert = true
self.appState.alert = Alert(
title: Text("An error occured!"),
message: Text(errorMsg ?? "unknown error"))*/
return
}
self.userVM = UserVM(user)
}
}
}
}
}
struct ProfileView_Previews: PreviewProvider {
static let viewModel = ProfileView.ViewModel()
static var previews: some View {
let appState = AppState()
appState.activeScreen = .profile
return ProfileView()
.environmentObject(appState)
}
}

Related

Swift: Can't change class attribute from #objc function

I'm trying to capture ProximitySensor activity on SwiftUI.
So I've created a class ProximityOberver and trying to update the attribute 'state' in the notification:
import SwiftUI
import UIKit
class ProximityObserver {
#State var state = false;
#objc func didChange(notification: NSNotification) {
print("MyView::ProximityObserver.didChange")
if let device = notification.object as? UIDevice {
print(device.proximityState)
state = device.proximityState
print(state)
}
}
}
struct ContentView: View {
#State var proximityObserver = ProximityObserver()
func activateProximitySensor() {
print("MyView::activateProximitySensor")
if !UIDevice.current.isProximityMonitoringEnabled {
UIDevice.current.isProximityMonitoringEnabled = true
if UIDevice.current.isProximityMonitoringEnabled {
NotificationCenter.default.addObserver(proximityObserver, selector: #selector(proximityObserver.didChange), name: UIDevice.proximityStateDidChangeNotification, object: UIDevice.current)
}
}
}
func deactivateProximitySensor() {
print("MyView::deactivateProximitySensor")
UIDevice.current.isProximityMonitoringEnabled = false
NotificationCenter.default.removeObserver(proximityObserver, name: UIDevice.proximityStateDidChangeNotification, object: UIDevice.current)
}
var body: some View {
Text(proximityObserver.state ? "true" : "false" )
.animation(.linear(duration: 20).delay(20), value: proximityObserver.state)
.onAppear() {
self.activateProximitySensor()
}
.onDisappear() {
self.deactivateProximitySensor()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But even 'state = device.proximityState' code executed, the following print(state) shows the attribute never changed.
MyView::ProximityObserver.didChange
true
false
Can someone explain why this happens, and how to fix this?
Thank you for the comment.
I could fix this as suggested.
class ProximityObserver: ObservableObject {
#Published var state = false;
#objc func didChange(notification: NSNotification) {
print("MyView::ProximityObserver.didChange")
if let device = notification.object as? UIDevice {
print(device.proximityState)
self.state = device.proximityState
print(state, device.proximityState)
}
}
}
struct ContentView: View {
#ObservedObject var proximityObserver = ProximityObserver()
...

Kotlin: Edit icon dashboard of icons between fragments

I'm trying to figure out the most efficient way to structure this problem..
I'd like to click on the 'EDIT' icon in the dashboard of the MainFragment, display a DialogFragment, allow user to select/deselect up to 5 icons, save the selection, close the DialogFragment, and update the MainFragment.
Should I use MutableLiveData/Observer from a ViewModel? Or is there a better approach? I currently cannot figure out how to use the ViewModel approach correctly...
So far, this is the code I have:
MainFragment: https://i.stack.imgur.com/5fRt2.png
DialogFragment: https://i.stack.imgur.com/ZvW3d.png
ViewModel Class:
class IconDashboardViewModel() : ViewModel(){
var liveDataDashIcons: MutableLiveData<MutableList<String>> = MutableLiveData()
var liveItemData: MutableLiveData<String> = MutableLiveData()
// Observer for live list
fun getLiveDataObserver(): MutableLiveData<MutableList<String>> {
return liveDataDashIcons
}
// Observer for each icon
fun getLiveItemObserver(): MutableLiveData<String> {
return liveItemData
}
// Set icon list
fun setLiveDashIconsList(iconList: MutableLiveData<MutableList<String>>) {
liveDataDashIcons.value = iconList.value
}
// Set data for data
fun setItemData(icon : MutableLiveData<String>) {
liveItemData.value = icon.toString()
}
var iconList = mutableListOf<String>()
}
MainFragment:
private fun populateIconList() : MutableLiveData<MutableList> {
var iconList = viewModel.liveDataDashIcons
// Roster icon
if (roster_dash_layout.visibility == View.VISIBLE) {
iconList.value!!.add(getString(R.string.roster))
} else {
if (iconList.value!!.contains(getString(R.string.roster))) {
iconList.value!!.remove(getString(R.string.roster))
}
}
}
DialogFragment:
private fun setIconList(iconList: MutableList){
var iconList = viewModel.iconList
Log.d(TAG, "viewModel iconList = " + iconList)
if (iconList.contains(getString(R.string.roster))) {
binding.radioButtonRosterPick.setBackgroundResource(R.drawable.icon_helmet_blue_bg)
}
}

How to make API call in swiftUI

I am trying to use PokeApi to make a Pokedex app. I just started swift a couple of days ago so I'm following a tutorial here: https://www.youtube.com/watch?v=UsO-84Xnhww. The tutorial doesn't seem to work, and I don't know how to access the PokeAPI in order to make this app. My code is posted below:
ContentView:
import SwiftUI
struct ContentView: View {
#State var searchText = ""
var pokemon = [Pokemon]()
var body: some View {
NavigationView {
List{
ForEach(searchText == "" ? pokemon : pokemon.filter({
$0.name.contains(searchText.lowercased())
})) { entry in
HStack {
Circle() //Pokemon Image
NavigationLink("\(entry.name)".capitalized, destination: Text("Detail view for \(entry.name)"))
}
}
}
.onAppear {
PokemonManager().getData() { pokemon in self.pokemon = pokemon
for pokemon in pokemon {
print(pokemon.name)
}
}
}
.searchable(text: $searchText)
.navigationTitle("PokePass")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PokemonAPI:
import Foundation
struct CurrentPokemon: Codable {
var results: [Pokemon]
}
struct Pokemon: Codable, Identifiable {
var id = UUID()
var name: String
var url: String
}
class PokemonManager {
func getData(completion: #escaping ([Pokemon]) -> ()) {
guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?limit=151") else {
return
}
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let pokemonList = try! JSONDecoder().decode(CurrentPokemon.self, from: data)
DispatchQueue.main.async {
completion(pokemonList.results)
}
}
.resume()
}
}
Use let id = UUID() in Pokemon, this will avoid decoding it, and that is what you want, since id is not part of the data.
You can also use this approach:
struct Pokemon: Codable, Identifiable {
var id = UUID()
var name: String
var url: String
enum CodingKeys: String, CodingKey {
case name, url
}
}
EDIT-1
and use #State var pokemon = [Pokemon]() in ContentView

Observing Firebase Authenfication with ObservableObject

I am trying to observe firebase authentification and update my View accordingly.
I have an SessionStore object:
class SessionStore: ObservableObject {
#Published var session: Account?
var handle: AuthStateDidChangeListenerHandle?
deinit {
stopListen()
}
func listen() {
if handle == nil {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("User logged in: \(user)")
self.session = Account.preData
} else {
self.session = nil
}
}
}
}
func stopListen() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
}
I use it in a view like this:
struct TabBarView: View {
#EnvironmentObject var sessionStore: SessionStore
#State var selectedTab = Tab.swiping
enum Tab: Int {
case swiping, matches, profil
}
func getUser() {
sessionStore.listen()
}
var body: some View {
Group {
if (sessionStore.session != nil) {
TabView(selection: $selectedTab) {
SwipingView().tabItem {
TabBarItem(text: "Text", image: "pause.circle")
}.tag(Tab.swiping)
}
} else {
LoginView()
}
}.onAppear(perform: getUser).onDisappear(perform: sessionStore.stopListen)
}
}
And call it like this:
sessionStore = SessionStore()
TabBarView().environmentObject(sessionStore!)
But it is only showing the LoginView even when the session is not nil. I made some code changes this is actually the solution.
I think this is the way to do that
class SessionStore: ObservableObject {
#Published var session: Account?
Also you referenced self inside the state closure meaning your object will never deinit. Add unowned or weak like so:
handle = Auth.auth().addStateDidChangeListener { [unowned self] (auth, user) in
https://www.avanderlee.com/swift/weak-self/
The correct implementation is:
class SessionStore: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var session: Account? {
didSet {
objectWillChange.send()
}
}
var handle: AuthStateDidChangeListenerHandle?
deinit {
stopListen()
}
func listen() {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("User logged in: \(user)")
self.session = Account.preData
} else {
self.session = nil
}
}
}
func stopListen() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
}
More info about it:
https://www.pointfree.co/blog/posts/30-swiftui-and-state-management-corrections

How do install a click handler on a dynamic listview (in tornadofx)

My application needs to permit additions to the listview. I've figured out how I can dynamically add to a listview by using observableArrayList. If I click on the button, an item gets added to the list and displayed.
Now I'm struggling to add a click handler (I want to handle the event that happens when someone clicks on any item within the list view). Where do I do this?
Here is my code.
package someapp
import javafx.collections.FXCollections
import javafx.geometry.Pos
import javafx.scene.layout.VBox
import javafx.scene.text.FontWeight
import tornadofx.*
class MyApp : App(HelloWorld::class) {
}
class HelloWorld : View() {
val leftSide: LeftSide by inject()
override val root = borderpane {
left = leftSide.root
}
}
class LeftSide: View() {
var requestView: RequestView by singleAssign()
override val root = VBox()
init {
with(root) {
requestView = RequestView()
this += requestView
this += button("Add Item") {
action {
requestView.responses.add( Request( "example.com",
"/foo/bar",
"{ \"foo\" : \"bar\"}".toByteArray()))
}
}
}
}
}
class RequestView : View() {
val responses = FXCollections.observableArrayList<Request>(
)
override val root = listview(responses) {
cellFormat {
graphic = cache {
form {
fieldset {
label(it.hostname) {
alignment = Pos.CENTER_RIGHT
style {
fontSize = 22.px
fontWeight = FontWeight.BOLD
}
}
field("Path") {
label(it.path)
}
}
}
}
}
}
}
class Request(val hostname: String, val path: String, val body: ByteArray) {
}
To configure a callback when an item in a ListView is selected, use the onUserSelect callback:
onUserSelect {
information("You selected $it")
}
You can optionally pass how many clicks constitutes a select as well, default is 2:
onUserSelect(1) {
information("You selected $it")
}
You are using some outdated constructs in your code, here is an updated version converted to best practices :)
class MyApp : App(HelloWorld::class)
class HelloWorld : View() {
override val root = borderpane {
left(LeftSide::class)
}
}
class LeftSide : View() {
val requestView: RequestView by inject()
override val root = vbox {
add(requestView)
button("Add Item").action {
requestView.responses.add(Request("example.com",
"/foo/bar",
"""{ "foo" : "bar"}""".toByteArray()))
}
}
}
class RequestView : View() {
val responses = FXCollections.observableArrayList<Request>()
override val root = listview(responses) {
cellFormat {
graphic = cache {
form {
fieldset {
label(it.hostname) {
alignment = Pos.CENTER_RIGHT
style {
fontSize = 22.px
fontWeight = FontWeight.BOLD
}
}
field("Path") {
label(it.path)
}
}
}
}
}
onUserSelect(1) {
information("You selected $it")
}
}
}
class Request(val hostname: String, val path: String, val body: ByteArray)