Compiler warning when setting up SceneDelegate for #EnvironmentObject - swift

I'm trying to implement #EnvironmentObject to pass an array from a View in one tab to another view in another tab.
I get the yellow compiler warning:
Initialization of immutable value 'reportView' was never used;
consider replacing with assignment to '_' or removing it
in SceneDelegate.swift
Here is my SceneDelegate.swift:
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var questionsAsked = QuestionsAsked()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
ProductsStore.shared.initializeProducts()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
let reportView = ReportView().environmentObject(questionsAsked)
}
}
Here is my ObservabelObject:
import Foundation
class QuestionsAsked: ObservableObject {
#Published var sectionThings = [SectionThing]()
#Published var memoryPalaceThings = [MemoryPalaceThing]()
}
I use:
#EnvironmentObject var questionsAsked: QuestionsAsked
in my view that generates the data to be passed around.
I pass in the data like so:
questionsAsked.sectionThings = testSectionThings ?? []
In the view where the data is to be passed to I have:
#EnvironmentObject var questionsAsked: QuestionsAsked
I then access it as follows:
totalThings += questionsAsked.sectionThings.count
totalThings += questionsAsked.memoryPalaceThings.count

The issue here is that the line in question is useless as it will not be presented in the Scene. The only view that is presented is MotherView. In order to pass questionsAsked down to that view you just append it like the other .environmentObject. So this should read:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// this really need to be #StateObject´s
#StateObject var viewRouter = ViewRouter()
#StateObject var questionsAsked = QuestionsAsked()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
ProductsStore.shared.initializeProducts()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
//Inject them here into the MotherView
window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(viewRouter).environmentObject(questionsAsked))
self.window = window
window.makeKeyAndVisible()
}
}
}
In the MotherView and descending Views you can now access them by the #EnvironmentObject wrapper.

Related

What is equivalent to 'window.rootViewController' in WindowGroup - SwiftUI

I am new to SwiftUI and facing a problem where I want to change the root view when a certain action occurs inside the app.
How I handle it when using SceneDelegate was as follows
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
... // the code of initializing the window
observeOnChangeWindow()
}
func observeOnChangeWindow(){
NotificationCenter.default.addObserver(self, selector: #selector(self.performChangeWindow), name: Notification.Name(K.changeWindowNotificationName), object: nil)
}
#objc func performChangeWindow() {
self.window?.rootViewController = UIHostingController(rootView: SplashScreenView())
}
However, I am not currently using SceneDelegate as I am initializing the app using WindowGroup
struct MyApp: App {
var body: some Scene {
WindowGroup {
SplashScreenView()
}
}
}
My question is :
How can I perform the same thing I am doing using SceneDelegate now ?
With the help of comments and some tutorials, I reached out to the solution (Tested on iOS 15, Xcode 13.2.1):
Add the following code to the Main App Launcher.
struct MyApp: App {
#StateObject var appState = AppState.shared
var body: some Scene {
WindowGroup {
SplashScreenView().id(appState.environment)
}
}
}
And then I created the AppState class, which is the class that when changes I will change the window.
class AppState: ObservableObject {
static let shared = AppState()
#Published var environment = "Production"
}
And whenever I wanted to change the environment and do the same functionality of changing window in UIKit , do the following :
AppState.shared.environment = "Pre-Production"

Converting SceneDelegate Enviromental Object to AppMain

Feel free to edit my title for better clarity.
I am starting a new iOS project and am no longer using SceneDelegate/AppDelegate. My problem is I want my ObservableObject to be an Environmental Object for my entire project but am having trouble converting and finding recent examples.
This is how I defined it in my previous iOS 13 project.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
//Environmental View
let observer = GlobalObserver()
let baseView = SplashScreenView().environment(\.managedObjectContext, context)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: baseView.environmentObject(observer))
self.window = window
window.makeKeyAndVisible()
}
}
Here is my main simplified
#main
struct DefaultApp: App {
//Environmental View
let observer: GlobalObserver
init(){
observer = GlobalObserver()
}
var body: some Scene {
WindowGroup {
LoginView()
//.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
The project generated a PersistenceController which I presume had to do with local storage. Do I need to somehow pass my observer into the .environment for loginView?
Yes, it's exactly as you have it in your example code, except that for some reason you commented it all out. So for instance:
class Thing : ObservableObject {
#Published var name = "Matt"
}
#main
struct SwiftUIApp: App {
#StateObject var thing = Thing()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(thing)
}
}
}
Now in any View struct you just say
#EnvironmentObject var thing : Thing
and presto, you're observing from the global instance.

Use ViewRouter in SwiftUI Project in Xcode 12

I tried to use this method (see link attached) in a new Xcode 12 project as a way to create a login page for a SwiftUI app, but I had the Problem not knowing what to add to the main App struct. I'm still a beginner and tried adding ContentView().environmentObject(ViewRouter()) to the WindowGroup in the main app struct. Am I totally wrong or why doesn't Xcode build the view? Can somebody help?
Below the working code snippet:
import SwiftUI
import Foundation
import Combine
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
}
. . .
}
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter,Never>()
var currentPage: String = "page1" {
didSet {
withAnimation() {
objectWillChange.send(self)
}
}
}
}
struct MotherView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "page1" {
ContentViewA()
} else if viewRouter.currentPage == "page2" {
ContentViewB()
.transition(.scale)
}
}
}
}
struct ContentViewA : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
Button(action: {self.viewRouter.currentPage = "page2"}) {
Text("Login")
}
}
}
struct ContentViewB : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
Button(action: {self.viewRouter.currentPage = "page1"}) {
Text("Logout")
}
}
}
Now I want to substitute the SceneDelegate in the Xcode 12 style, but the following doesn't work. Any idea why?
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
MotherView().environmentObject(ViewRouter())
}
}
}
Pls try next steps:
remove whole SceneDelegate class (in your case no need SceneDelegate class)
Modify your ViewRouter class like:
class ViewRouter: ObservableObject {
#Published var currentPage: String = "page1"
}

Using #EnvironmentObject properties with CADisplayLink

I'm trying to implement CADisplayLink for some animations, but when I try to access my MainData environment object properties from inside class MyAnimations, I get the fatal error No ObservableObject of type MainData found. A View.environmentObject(_:) for MainData may be missing as an ancestor of this view.
In SceneDelegate, I have MainData set as an environment object on ContentView:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var mainData = MainData()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(self.mainData))
self.window = window
window.makeKeyAndVisible()
}
}
...
}
And here's the class with CADisplayLink. createDisplayLink() is called from ContentView:
class MyAnimations: NSObject{
#EnvironmentObject var mainData: MainData
func createDisplayLink() {
let displaylink = CADisplayLink(target: self, selector: #selector(step))
displaylink.add(to: .current, forMode: RunLoop.Mode.default)
}
#objc func step(link: CADisplayLink) {
mainData.displayLinkY += 1.5 //Error here
mainData.displayLinkX += 1.5
}
}
My question is: how can I change environment object properties displayLinkX and displayLinkY from inside step()?
Just remove #EnvironmentObject property wrapper, it is for SwiftUI only
class MyAnimations: NSObject{
var mainData: MainData
init(mainData: MainData) {
self.mainData = mainData
super.init()
}
// ... other code
}

How do I catch a value change that was changed via a function in SwiftUI?

My View don't check that's the value has changed. Can you tell me why or can you help me?
You can see below the important code.
My Class
import SwiftUI
import SwiftyStoreKit
class Foo: ObservableObject {
#Published var Trigger : Bool = false
func VerifyPurchase() {
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result {
case .success(let receipt):
let productId = "com.musevisions.SwiftyStoreKit.Purchase1"
// Verify the purchase of Consumable or NonConsumable
let purchaseResult = SwiftyStoreKit.verifyPurchase(
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let receiptItem):
print("\(productId) is purchased: \(receiptItem)")
self.Trigger.toggle()
case .notPurchased:
print("The user has never purchased \(productId)")
self.Trigger.toggle()
}
case .error(let error):
print("Receipt verification failed: \(error)")
self.Trigger.toggle()
}
}
}
}
In SceneDelegate my important code at sceneDidbecom to trigger the value to true and then if the function completed, then I want that's trigger back to false
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let foo = Foo()
let contentView = ContenView(foo: Foo).environment(\.managedObjectContext, context)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidBecomeActive(_ scene: UIScene) {
let foo = Foo()
foo.Trigger = true
foo.VerifyPurchase()
}
My View that's doesnt update self when the Value has changed.
struct ContentView: View {
#ObservedObject var foo: Foo
var body: some View {
Text(self.foo.Trigger ? "true" : "false")
}
}
SwiftyStoreKit.verifyReceipt is an asynchronous function and #Published variables must be updated on the main thread.
Try adding DispatchQueue.main.async when you change the Trigger variable in the background:
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
...
DispatchQueue.main.async {
self.Trigger = false
}
}
Also note that variables in Swift are usually lowercased - ie. trigger instead of Trigger.
You're also using two different Foo instances in your project.
I recommend you remove the code from the func sceneDidBecomeActive(_ scene: UIScene) and move it to the .onAppear in the ContentView:
struct ContentView: View {
#ObservedObject var foo: Foo
var body: some View {
Text(self.foo.Trigger ? "true" : "false")
.onAppear {
foo.Trigger = true
foo.VerifyPurchase()
}
}
}
Also instead of
Text(self.foo.Trigger ? "true" : "false")
you can do
Text(String(describing: self.foo.Trigger))