I have a class called Startup. In this class, I have a bunch of functions that I call from SceneDelegate when the app starts.
for example:
class Startup {
func doThis() {
print("I'm doing this")
}
}
At one point, it may download data, if its necessary. When this happens, I'd like to trigger an Alert w/ Activity Indicator to block the UI from user input until things are completed.
I have the Activity Indicator Alert all set up in ContentView, which triggers on an EnvironmentObject var Bool. So all I need to do to activate this UI blocking alert is to toggle() my env var.
The problem I am having is that I can not trigger this env var from within my class. I have tried the following:
When I put the #EnvironmentObject var dataBusy: DataBusy
and call it from within a function: dataBusy.isBusy = true, I get the error message:
Fatal error: No ObservableObject of type DataBusy found.
Which indicates that I need to shove the env object into the environment of the class when it is instantiated, however, when I try to do that, I get:
Value of tuple type '()' has no member 'environmentObject'
So, I can not add this env var into this class object.
Trying to use:
#ObservedObject var dataBusy = DataBusy()
In my class seems to not error out, but toggling this does not do anything to trigger my event.
I can't think of any other way to communicate with my View from this startup class.
Any ideas?
The following code works, you many find what you miss or misunderstanding.
class Env: NSObject, ObservableObject{
#Published var isEnabled = false
}
struct AlertTest: View {
#EnvironmentObject var envObject: Env
var body: some View {
Text("board").alert(isPresented: $envObject.isEnabled) { () -> Alert in
return Alert(title: Text("hello"))
}
}
}
//Scene Delegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let m = Env()
let contentView = AlertTest().environmentObject(m)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (Timer) in
DispatchQueue.main.async {
m.isEnabled = true
}
}
}
Got it..
E.Coms answer is very good, however, I was calling the func which would be responsible for this action from within another function, within another function from this other class, not directly from the scene delegate.
To get it, I had to pass the DataBusy object dataBusy from my scene delegate to my class func as a parameter:
//Scene Delegate:
let dataBusy = DataBusy() //my Observed Object Class
let myClass = MyClass(); //instantiate my class
myClass.myFunc(data: dataBusy) //pass the object through to the class function
This way, when the time arises within my class function, I can toggle the variable and it all works perfectly.
Thank you for your input.
Related
my first view can get all the data from API request, then opened second view to change the API request data, it crashed.
below is the error
Fatal error: No ObservableObject of type NetworkManager found.
A View.environmentObject(_:) for NetworkManager may be missing as an ancestor of this view.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Monoceros_Sim/Monoceros-39.3/Core/EnvironmentObject.swift, line 55
2019-11-07 12:00:01.961425-0800 EarthQuake[73703:5913116] Fatal error: No ObservableObject of type NetworkManager found.
A View.environmentObject(_:) for NetworkManager may be missing as an ancestor of this view.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Monoceros_Sim/Monoceros-39.3/Core/EnvironmentObject.swift, line 55
This is swift file to fetch data from API, then save data to posts
import Foundation
import Combine
final class NetworkManager: ObservableObject {
#Published var posts = [Post]()
func fetchData() {
//some code to fetch data, then save to posts
}
}
In my first list view file: EarthQuakeList.swift
import SwiftUI
struct EarthQuakeList: View {
#EnvironmentObject var networkManager: NetworkManager
//#ObservedObject var networkManager = NetworkManager()
var body: some View {
NavigationView {
List(networkManager.posts) { post in
NavigationLink(destination: EarthQuakeDetail(detail: post.properties, location: post.locationCoordinate)) {
ListRow(magnitude: post.properties.mag ?? 0.0, place: post.properties.place)
}
}
.navigationBarTitle(Text("Today"))
.navigationBarItems(
trailing:
VStack {
Button(action: {
print("Edit button tapped")
self.showEditPage.toggle()
}) {
//Top right icon
Image(systemName: "pencil.circle")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
}.sheet(isPresented: $showEditPage) {
return EditPage().environmentObject(self.networkManager)
}
}
)
}
.onAppear {
//Call function to fetch data
self.networkManager.fetchData()
}
}
}
Everything works well, I can see all the data, but when I do the same thing in my second view file, I get the data back, but it does not update for the first view
In my second editing view file: EditPage.swift
import SwiftUI
struct EditPage: View {
//#ObservedObject var networkManager = NetworkManager()
#EnvironmentObject var networkManager: NetworkManager
var body: some View {
VStack {
Text("Edite")
}
.onAppear(){
//I called the same function to get data as in first view file, but it is not updating the list view
self.networkManager.fetchData()
}
}
}
SceneDelegate.swift
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Set up environment object
let contentView = EarthQuakeList().environmentObject(NetworkManager())
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(
rootView: contentView
)
self.window = window
window.makeKeyAndVisible()
}
}
So I am expecting second view file to call function self.networkManager.fetchData() to get data from API call, then update list view in first swift file EarthQuakeList, the issue is I am just getting correct data back, the view is not updating with new data
The whole issue is very easy to fix just add self.networkManager # EarthQuakeList.swift
.sheet(isPresented: $showEditPage) {
return EditPage().environmentObject(self.networkManager)
I got the reason and inspired from article:
https://github.com/peterfriese/Swift-EnvironmentObject-Demo
if you are using sheet ore navigation you have to pass the environment Object maybe its a bug of swiftUI but it works for me in Xcode 11.5:
.sheet(isPresented: $show){
mySheetView().environmentObject(self.myEnviromentObject)
}
NOTE:This answer was given before the question was modified to represent the next problem the developer ran into.
This problem seems to be cause by using two instances of NetworkManager in your code.
If you add this line to the NetworkManager:
final class NetworkManager: … {
static let shared = NetworkManager()
}
And then update the references in your two views like this:
struct EarthQuakeList: … {
#ObservedObject var networkManager = NetworkManager.shared
…
}
struct EditPage: … {
#ObservedObject var networkManager = NetworkManager.shared
…
}
This will make sure both views use the exact same object to access the information. By using the same object, updates in one view will cause the other view to be updated as well.
Once this works, I recommend to play around with .environmentObject() (example) in SwiftUI as this would allow for sharing the same instance of these service types across your application.
Good luck with your project!
I've had this in a situation where I had
open Base
{
}
that a class in my framework was relying on and
#EnvironmentObject var base: Base was declared
while the app had class Derived: Base {...}
Environment had to be reinjected like so:
ViewInAFramework.environmentObject(derived as Base)
I think may be you have used the keyword final to the class networkManager
Trying removing the keyword and try
As final Keyword is used if we don't want to create any further instance of that class , Its's not working because you have created an instance of property wrapper EnvironmentObject
Try Without final class
I've got an application written in swift/swiftUI.
the logic part composed of events generator that enable the client to register a callback for each event before the UI part starts.
init.swift
-----------
func applicationDidFinishLaunching(_ aNotification: Notification) {
let event1Handler : eventHandler = {(data) in
ContentView().alert_handler.showAlert = true
}
let event2Handler : eventHandler = {(data) in
...
}
eventGenerator.sharedInstance().registerUIEvent1(event1Handler, event2: event2Handler)
window = NSWindow(...)
window.contentView = NSHostingView(rootView: ContentView())
...
}
in the UI part, there's an optional alert that is presented depent on the showAlert that can be set from the closure from the previous file...
class alertHandler: ObservableObject {
#Published var showAlert = false
}
struct ContentView: View {
#ObservedObject var alert_handler = alertHandler()
var body: some View {
GeometryReader { metrics in
.....
}.alert(isPresented: $alert_handler.showAlert, content: {
Alert(title: Text("Alert:"),
message: Text("press OK to execute default action..."),
dismissButton: Alert.Button.default(
Text("Press ok here"), action: { print("Hello world!") }
)
)
})
Unfortunately I couldn't see the alert appears when the first event was triggered. Perhaps anyone can tell me if I'd doing anything wrong ? or suggest an alternative approach to modify swiftUI struct variable (event_handler) from remote closure ?
I believe that my the problem may derived from ContentView module which is not a singleton, so when I set showAlert I'm doing so for another instance of ContentView. How can I fix my code to access showAlert that belongs to the currently running instance of ContentView ?
Your suspicion about ContentView not being a singleton instance is correct. You can solve this by owning your alertHandler in the parent (in this case, the app delegate) and passing that down to ContentView.
var handler = alertHandler()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let event1Handler : eventHandler = { (data) in
self.handler.showAlert = true //<-- Here
}
let event2Handler : eventHandler = {(data) in
...
}
eventGenerator.sharedInstance().registerUIEvent1(event1Handler, event2: event2Handler)
window = NSWindow(...)
window.contentView = NSHostingView(rootView: ContentView(alert_handler: handler))
...
}
struct ContentView: View {
#ObservedObject var alert_handler : alertHandler //<-- Here
That way, when you modify the showAlert property, the ContentView gets updated because it's the same instance of alertHandler.
Note: I'd consider adopting the Swift conventions of capitalizing type names and using camel case rather than snake case -- it'll make it easier for others to read your code.
The goal is to have easy access to hosting window at any level of SwiftUI view hierarchy. The purpose might be different - close the window, resign first responder, replace root view or contentViewController. Integration with UIKit/AppKit also sometimes require path via window, so…
What I met here and tried before,
something like this
let keyWindow = shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
or via added in every SwiftUI view UIViewRepresentable/NSViewRepresentable to get the window using view.window looks ugly, heavy, and not usable.
Thus, how would I do that?
SwiftUI Lift-Cycle (SwiftUI 2+)
Here is a solution (tested with Xcode 13.4), to be brief only for iOS
We need application delegate to create scene configuration with our scene delegate class
#main
struct PlayOn_iOSApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
// ...
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
if connectingSceneSession.role == .windowApplication {
configuration.delegateClass = SceneDelegate.self
}
return configuration
}
}
Declare our SceneDelegate and confirm it to both (!!!+) UIWindowSceneDelegate and ObservableObject
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
var window: UIWindow? // << contract of `UIWindowSceneDelegate`
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
self.window = windowScene.keyWindow // << store !!!
}
}
Now we can use our delegate anywhere (!!!) in view hierarchy as EnvironmentObject, because (bonus of confirming to ObservableObject) SwiftUI automatically injects it into ContentView
#EnvironmentObject var sceneDelegate: SceneDelegate
var body: some View {
// ...
.onAppear {
if let myWindow = sceneDelegate.window {
print(">> window: \(myWindow.description)")
}
}
}
Complete code in project is here
UIKit Life-Cycle
Here is the result of my experiments that looks appropriate for me, so one might find it helpful as well. Tested with Xcode 11.2 / iOS 13.2 / macOS 15.0
The idea is to use native SwiftUI Environment concept, because once injected environment value becomes available for entire view hierarchy automatically. So
Define Environment key. Note, it needs to remember to avoid reference cycling on kept window
struct HostingWindowKey: EnvironmentKey {
#if canImport(UIKit)
typealias WrappedValue = UIWindow
#elseif canImport(AppKit)
typealias WrappedValue = NSWindow
#else
#error("Unsupported platform")
#endif
typealias Value = () -> WrappedValue? // needed for weak link
static let defaultValue: Self.Value = { nil }
}
extension EnvironmentValues {
var hostingWindow: HostingWindowKey.Value {
get {
return self[HostingWindowKey.self]
}
set {
self[HostingWindowKey.self] = newValue
}
}
}
Inject hosting window in root ContentView in place of window creation (either in AppDelegate or in SceneDelegate, just once
// window created here
let contentView = ContentView()
.environment(\.hostingWindow, { [weak window] in
return window })
#if canImport(UIKit)
window.rootViewController = UIHostingController(rootView: contentView)
#elseif canImport(AppKit)
window.contentView = NSHostingView(rootView: contentView)
#else
#error("Unsupported platform")
#endif
use only where needed, just by declaring environment variable
struct ContentView: View {
#Environment(\.hostingWindow) var hostingWindow
var body: some View {
VStack {
Button("Action") {
// self.hostingWindow()?.close() // macOS
// self.hostingWindow()?.makeFirstResponder(nil) // macOS
// self.hostingWindow()?.resignFirstResponder() // iOS
// self.hostingWindow()?.rootViewController?.present(UIKitController(), animating: true)
}
}
}
}
Add the window as a property in an environment object. This can be an existing object that you use for other app-wide data.
final class AppData: ObservableObject {
let window: UIWindow? // Will be nil in SwiftUI previewers
init(window: UIWindow? = nil) {
self.window = window
}
}
Set the property when you create the environment object. Add the object to the view at the base of your view hierarchy, such as the root view.
let window = UIWindow(windowScene: windowScene) // Or however you initially get the window
let rootView = RootView().environmentObject(AppData(window: window))
Finally, use the window in your view.
struct MyView: View {
#EnvironmentObject private var appData: AppData
// Use appData.window in your view's body.
}
Access the current window by receiving NSWindow.didBecomeKeyNotification:
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)) { notification in
if let window = notification.object as? NSWindow {
// ...
}
}
At first I liked the answer given by #Asperi, but when trying it in my own environment I found it difficult to get working due to my need to know the root view at the time I create the window (hence I don't know the window at the time I create the root view). So I followed his example, but instead of an environment value I chose to use an environment object. This has much the same effect, but was easier for me to get working. The following is the code that I use. Note that I have created a generic class that creates an NSWindowController given a SwiftUI view. (Note that the userDefaultsManager is another object that I need in most of the windows in my application. But I think if you remove that line plus the appDelegate line you would end up with a solution that would work pretty much anywhere.)
class RootViewWindowController<RootView : View>: NSWindowController {
convenience init(_ title: String,
withView rootView: RootView,
andInitialSize initialSize: NSSize = NSSize(width: 400, height: 500))
{
let appDelegate: AppDelegate = NSApplication.shared.delegate as! AppDelegate
let windowWrapper = NSWindowWrapper()
let actualRootView = rootView
.frame(width: initialSize.width, height: initialSize.height)
.environmentObject(appDelegate.userDefaultsManager)
.environmentObject(windowWrapper)
let hostingController = NSHostingController(rootView: actualRootView)
let window = NSWindow(contentViewController: hostingController)
window.setContentSize(initialSize)
window.title = title
windowWrapper.rootWindow = window
self.init(window: window)
}
}
final class NSWindowWrapper: ObservableObject {
#Published var rootWindow: NSWindow? = nil
}
Then in my view where I need it (in order to close the window at the appropriate time), my struct begins as the following:
struct SubscribeToProFeaturesView: View {
#State var showingEnlargedImage = false
#EnvironmentObject var rootWindowWrapper: NSWindowWrapper
var body: some View {
VStack {
Text("Professional Version Upgrade")
.font(.headline)
VStack(alignment: .leading) {
And in the button where I need to close the window I have
self.rootWindowWrapper.rootWindow?.close()
It's not quite as clean as I would like it to be (I would prefer to have a solution where I did just say self.rootWindow?.close() instead of requiring the wrapper class), but it isn't bad and it allows me to create the rootView object before I create the window.
Instead of ProjectName_App use old fashioned AppDelegate approach as app entry point.
#main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
...
}
}
Then pass window as environment object. For example:
struct WindowKey: EnvironmentKey {
static let defaultValue: UIWindow? = nil
}
extension EnvironmentValues {
var window: WindowKey.Value {
get { return self[WindowKey.self] }
set { self[WindowKey.self] = newValue }
}
}
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
let rootView = RootView()
.environment(\.window, window)
window?.rootViewController = UIHostingController(rootView: rootView)
window?.makeKeyAndVisible()
}
}
And use it when need it.
struct ListCell: View {
#Environment(\.window) private var window
var body: some View {
Rectangle()
.onTapGesture(perform: share)
}
private func share() {
let vc = UIActivityViewController(activityItems: [], applicationActivities: nil)
window?.rootViewController?.present(vc, animated: true)
}
}
2022, macOS only
Maybe not best solution, but works well for me and enough universal for almost any situation
Usage:
someView()
.wndAccessor {
$0?.title = String(localized: "All you need to know about FileBo in ONE minute")
}
extension code:
import SwiftUI
#available(OSX 11.0, *)
public extension View {
func wndAccessor(_ act: #escaping (NSWindow?) -> () )
-> some View {
self.modifier(WndTitleConfigurer(act: act))
}
}
#available(OSX 11.0, *)
struct WndTitleConfigurer: ViewModifier {
let act: (NSWindow?) -> ()
#State var window: NSWindow? = nil
func body(content: Content) -> some View {
content
.getWindow($window)
.onChange(of: window, perform: act )
}
}
//////////////////////////////
///HELPERS
/////////////////////////////
// Don't use this:
// Usage:
//.getWindow($window)
//.onChange(of: window) { _ in
// if let wnd = window {
// wnd.level = .floating
// }
//}
#available(OSX 11.0, *)
private extension View {
func getWindow(_ wnd: Binding<NSWindow?>) -> some View {
self.background(WindowAccessor(window: wnd))
}
}
#available(OSX 11.0, *)
private struct WindowAccessor: NSViewRepresentable {
#Binding var window: NSWindow?
public func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
self.window = view.window
}
return view
}
public func updateNSView(_ nsView: NSView, context: Context) {}
}
I'm doing a sign-in screen. after the sign-in button click, I'll do an API call on the response I will redirect the user to home screen.
Q - How to navigate from one View to Another by a button click with some action.
I've tried this,
Programatically navigate to new view in SwiftUI
But I'm getting an error like,
"Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type"
Please help me to resolve it.
There is an issue with some View ATM (hope it will be fixed soon).
In order to use code posted, you will need to write something like:
struct ContentView: View {
#EnvironmentObject var userAuth: UserAuth
#ViewBuilder
var body: some View {
if !userAuth.isLoggedin {
return LoginView()
} else {
return NextView()
}
}
}
At the list, at the moment of writing, this was the only thing working for me - both for body and for Group.
Reference for future: date 24 Oct 2019.
Alternatively look at SceneDelgate.swift where you can set the root view of the key window to whatever you want.
In your situation when there is a successful login you can signal the change of state to the SceneDelegate (such as by Notification). Then have the app set the root view controller to your main View (as a UIHostingController).
For example:
In your SceneDelegate class add:
var currentScene: UIScene? // need to keep reference
Then inside the func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
Save a reference to the scene in the variable declared above.
self.currentScene = scene
Next, add a listener to when you want to change the key window with the new view:
var keyWindow: UIWindow?
NotificationCenter.default.addObserver(forName: .newUser, object: nil, queue: .main) { [weak self] (notification) in
guard let windowScene = self?.currentScene as? UIWindowScene else { return }
keyWindow = UIWindow(windowScene: windowScene)
keyWindow?.rootViewController = UIHostingController(rootView: Text("hello"))
keyWindow?.makeKeyAndVisible()
}
Just set the Notification to post whenever you need and replace the Text with whatever View you want.
I'm using an OperationQueue to upload files one by one to a remote server using URLSession.dataTask. A delegate is used to update a progress bar but after implementing OperationQueue my delegate becomes nil. It worked without OperationQueues. Looking at the stack while the program is running I don't see my progress bar's view controller. It's been a few days and I still can't quite figure it out. I'm guessing the view controller is getting deallocated but I'm not sure how to prevent it from getting deallocated. Thank you.
I have my delegate set to self in NetWorkViewController but inside my NetworkManager class's urlSession(didSendBodyData), the delegate becomes nil. The delegate is not weak and is a class variable.
However, my delegate becomes none-nil again within the completion block of my BlockOperation. This works for dismissing the ViewController via delegation. But the delegate is nil when I'm trying to update inside urlSession(didSendBodyData)...
UPDATE 10/30/2018
It seems that my urlSessions delegates are on a separate thread and is enqueued to the main thread when called but I lose reference to my custom delegate that updates the UI. I'm trying to read more about multithreading but any help would be appreciated!
UPDATE 2 10/30/2018
Solution found The issue was that I was creating another instance of NetworkManager inside each operation. This causes the delegate to be nil because a new instance of NetworkManager is being created for each operation. The fix is to pass self from the original NetworkManager so the delegate is retained.
uploadFiles
func uploadFiles(item: LocalEntry) {
let mainOperation = UploadMainFileOperation(file: item)
// This is where I need to give the operation its
// networkManager so the proper delegate is transferred.
mainOperation.networkManager = self
mainOperation.onDidUpload = { (uploadResult) in
if let result = uploadResult {
self.result.append(result)
}
}
if let lastOp = queue.operations.last {
mainOperation.addDependency(lastOp)
}
queue.addOperation(mainOperation)
....
....
let finishOperation = BlockOperation { [unowned self] in
self.dismissProgressController()
for result in self.result {
print(result)
}
self.delegate?.popToRootController()
}
if let lastOp = queue.operations.last {
finishOperation.addDependency(lastOp)
}
queue.addOperation(finishOperation)
queue.isSuspended = false
}
UploadMainFileOperation
class UploadMainFileOperation: NetworkOperation {
let file: LocalEntry
// First issue is here. I re-declared another NetworkManager that doesn't have its delegate properly set.
private let networkManager = NetworkManager()
// I have since have this class receive the original networkManager after it's declared.
var networkManager: NetworkManager?
var onDidUpload: ((_ uploadResult: String?) -> Void)!
init(file: LocalEntry) {
self.file = file
}
override func execute() {
uploadFile()
}
private func uploadFile() {
networkManager.uploadMainFile(item: file) {
(httpResult) in
self.onDidUpload(httpResult)
self.finished(error: "upload main")
}
}
}
urlSession(didSendBodyData)
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
// This is wrong.
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
updateDelegateWith(progress: uploadProgress)
// This is the correct way for my situation.
// Because each operation on the queue is on a separate thread. I need to update the UI from the main thread.
DispatchQueue.main.async {
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.updateDelegateWith(progress: uploadProgress)
}
}
updateDelegateWith(progress: Float)
func updateDelegateWith(progress: Float) {
delegate?.uploadProgressWith(progress: progress)
}
NetworkManagerViewController where the progress bar lives
class NetworkViewController: UIViewController, NetWorkManagerDelegate {
var localEntry: LocalEntry?
var progressBackground = UIView()
var progressBar = UIProgressView()
func uploadProgressWith(progress: Float) {
progressBar.progress = progress
view.layoutSubviews()
}
deinit {
print("deallocate")
}
override func viewDidLoad() {
super.viewDidLoad()
let networkManager = NetworkManager()
networkManager.delegate = self
networkManager.uploadFiles(item: self.localEntry!)
....
....
}
}
With the latest code shared, i would suggest to keep NetworkManager instance at the class level instead of a function level scope as this will ensure the networkManager instance is not deallocated.
class NetworkViewController: UIViewController, NetWorkManagerDelegate {
var localEntry: LocalEntry?
var progressBackground = UIView()
var progressBar = UIProgressView()
let networkManager = NetworkManager()
func uploadProgressWith(progress: Float) {
progressBar.progress = progress
view.layoutSubviews()
}
deinit {
print("deallocate")
}
override func viewDidLoad() {
super.viewDidLoad()
networkManager.delegate = self
networkManager.uploadFiles(item: self.localEntry!)
}
...
Also, you need to be careful for retain-cycles that cause memory leaks. To avoid retain cycles, you need to declare your delegate variable as weak.
As user #Kamran pointed out, I was creating a class level instance of networkManager inside UploadMainFileOperation. The issue has been fixed by changed that variable to an Optional and giving it an instance of NetworkManager, as self ,that was queueing up the operations. The code blocks as been updated with comments of the correct code along with the incorrect code.
If you set a delegate and later it becomes nil, this means your delegate has been deallocated.
I would recommend to create an (empty) deinit in your delegate class and set a breakpoint for the debugger in that method. This will help you find out where you're losing the reference to said delegate.
You can probably avoid this by assigning your delegate to a property of one of your classes or make it a strong reference in one of your completion blocks.