2

Please tell me, I have 2 classes. There is a load_shop() function that enables the display of the webView. If I call a function from the first class ViewController then the function is executed and the webView is displayed, and if from another class CheckUpdate the function is executed (print is output) but the webView is not displayed. What could be the problem? Thanks for any help. I'm new to this.

class ViewController: UIViewController {
   ...
   override func viewDidLoad() {
      super.viewDidLoad()
      self.load_shop()
   }
   ...
   func load_shop() {
      view = webView
      print("finish_update")
   }
}
class CheckUpdate: NSObject {
   if (...) {
      ViewController().load_shop()
   }
}

ViewController.swift

import UIKit
import WebKit
import UserNotifications
import Foundation

class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.scrollView.bounces = false;

        
        let myURL = URL(string:"https://google.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest);
        webView.navigationDelegate = self

    }
    
    var update = 0
    
    func load_shop() {
        view = webView
        print("finish_update")
        
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        CheckUpdate.shared.showUpdate(withConfirmation: false)
        
    }

    func close() {
        exit(0);
    }
    
    override var prefersHomeIndicatorAutoHidden: Bool {
        return true
    }
}

CheckUpdate.swift

import Foundation
import UIKit

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

class LookupResult: Decodable {
    var results: [AppInfo]
}

class AppInfo: Decodable {
    var version: String
    var trackViewUrl: String
}

class CheckUpdate: NSObject {


    let first_class  = ViewController()
    static let shared = CheckUpdate()
    
    
    
    
    func showUpdate(withConfirmation: Bool) {
        DispatchQueue.global().async {
            self.checkVersion(force : !withConfirmation)
        }
    }
  

    private  func checkVersion(force: Bool) {
        if let currentVersion = self.getBundle(key: "CFBundleShortVersionString") {
            _ = getAppInfo { (info, error) in
                if let appStoreAppVersion = info?.version {
                    if let error = error {
                        print("error getting app store version: ", error)
                    } else if appStoreAppVersion == currentVersion {
                        self.first_class.load_shop()
                        print("Already on the last app version: ",currentVersion)
                    } else {
                        print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
                        DispatchQueue.main.async {
                            let topController: UIViewController = (UIApplication.shared.windows.first?.rootViewController)!
                            topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
                        }
                    }
                }
            }
        }
    }

    private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
    
      // You should pay attention on the country that your app is located, in my case I put Brazil */br/*
      // Você deve prestar atenção em que país o app está disponível, no meu caso eu coloquei Brasil */br/*
      
        guard let identifier = self.getBundle(key: "CFBundleIdentifier"),
              let url = URL(string: "http://itunes.apple.com/br/lookup?bundleId=\(identifier)") else {
                DispatchQueue.main.async {
                    completion(nil, VersionError.invalidBundleInfo)
                }
                return nil
        }
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
          
            
                do {
                    if let error = error { throw error }
                    guard let data = data else { throw VersionError.invalidResponse }
                    
                    let result = try JSONDecoder().decode(LookupResult.self, from: data)
                    print(result.results)
                    guard let info = result.results.first else {
                        throw VersionError.invalidResponse
                    }

                    completion(info, nil)
                } catch {
                    completion(nil, error)
                }
            }
        
        task.resume()
        return task

    }

    func getBundle(key: String) -> String? {

        guard let filePath = Bundle.main.path(forResource: "Info", ofType: "plist") else {
          fatalError("Couldn't find file 'Info.plist'.")
        }
        // 2 - Add the file to a dictionary
        let plist = NSDictionary(contentsOfFile: filePath)
        // Check if the variable on plist exists
        guard let value = plist?.object(forKey: key) as? String else {
          fatalError("Couldn't find key '\(key)' in 'Info.plist'.")
        }
        return value
    }
}

extension UIViewController {
    @objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
        guard let appName = CheckUpdate.shared.getBundle(key: "CFBundleName") else { return } //Bundle.appName()

        let alertTitle = "New version"
        let alertMessage = "A new version of \(appName) are available on AppStore. Update now!"

        let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)

        if !Force {
            let notNowButton = UIAlertAction(title: "Not now", style: .default)
            alertController.addAction(notNowButton)
        }

        let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
            guard let url = URL(string: AppURL) else {
                return
            }
            if #available(iOS 10.0, *) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                UIApplication.shared.openURL(url)
            }
        }

        alertController.addAction(updateButton)
        self.present(alertController, animated: true, completion: nil)
    }
}
8
  • ViewController() creates a brand new instance which is not the instance you expect if you are using storyboard. You need the actual reference via segue or instantiation. And once again please name functions and variables lowerCamelCased according to the naming convention. Commented Nov 20, 2020 at 14:13
  • @vadian "You need the actual reference via segue or instantiation." What do you have in mind? Can you show with an example? Commented Nov 20, 2020 at 14:16
  • I just saw that CheckUpdate is not another view controller. How is the class related to ViewController? You might create an init method init(controller : ViewController) in CheckUpdate and pass the reference. Commented Nov 20, 2020 at 14:19
  • @vadian I don't think you understand me. The function is executed. The webView is not displayed. Commented Nov 20, 2020 at 14:55
  • 1
    I don't think you understand me. The function is executed in the new instance. But it's not the instance in the storyboard so the views are not connected. Commented Nov 20, 2020 at 14:59

2 Answers 2

1

let first_class = ViewController() // stop doing this. It will not work. Think of it; you have a car (view controller), you want to turn on the radio of your car. What would you have to do? First turn your car on and then its radio right? But what you do in your code is; instead of turning on the radio of your car, you go and get a new car (like new instance of viewcontroller) and turn on the radio of the new car. But actually you need to turn on the radio of your own car not the new car. This how works instances in object oriented programming. You created an instance of a UIViewController (it is your car), then in another class you are creating another instance of UIViewController (that is not your car in the example). Then you try to modify the first one but actually you are modifying the another one. As soon as I am home I will modify your code from my laptop, because it is hard to edit it from the mobile phone.

Here is the modified code. NOTE THAT I just correct the parts where you want to call load_shop function from the CheckUpdate class. I mean I don't know all the logic behind your code so, if it contains more logical errors it's all up to you.

ViewController.swift

import UIKit
import WebKit
import UserNotifications
import Foundation // Not really necesary cause UIKit import already imports it

class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.scrollView.bounces = false;

        
        let myURL = URL(string:"https://google.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest);
        webView.navigationDelegate = self

    }
    
    var update = 0
    
    func load_shop() {
        view = webView
        print("finish_update")
        
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        CheckUpdate.shared.showUpdate(withConfirmation: false, caller: self) // Attention here!!!
        
    }

    func close() {
        exit(0);
    }
    
    override var prefersHomeIndicatorAutoHidden: Bool {
        return true
    }
}

CheckUpdate.swift

import UIKit

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

class LookupResult: Decodable {
    var results: [AppInfo]
}

class AppInfo: Decodable {
    var version: String
    var trackViewUrl: String
}

class CheckUpdate: NSObject {


    let first_class: ViewController? // Attention here!!!
    static let shared = CheckUpdate()
    
    
    func showUpdate(withConfirmation: Bool, viewController: ViewController) {
        first_class = viewController // Attention here!!!
        DispatchQueue.global().async {
            self.checkVersion(force : !withConfirmation)
        }
    }
  

    private  func checkVersion(force: Bool) {
        if let currentVersion = self.getBundle(key: "CFBundleShortVersionString") {
            _ = getAppInfo { (info, error) in
                if let appStoreAppVersion = info?.version {
                    if let error = error {
                        print("error getting app store version: ", error)
                    } else if appStoreAppVersion == currentVersion {
                        self.first_class?.load_shop() // Atention here!!!
                        print("Already on the last app version: ",currentVersion)
                    } else {
                        print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
                        DispatchQueue.main.async {
                            let topController: UIViewController = (UIApplication.shared.windows.first?.rootViewController)!
                            topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
                        }
                    }
                }
            }
        }
    }

    private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
    
      // You should pay attention on the country that your app is located, in my case I put Brazil */br/*
      // Você deve prestar atenção em que país o app está disponível, no meu caso eu coloquei Brasil */br/*
      
        guard let identifier = self.getBundle(key: "CFBundleIdentifier"),
              let url = URL(string: "http://itunes.apple.com/br/lookup?bundleId=\(identifier)") else {
                DispatchQueue.main.async {
                    completion(nil, VersionError.invalidBundleInfo)
                }
                return nil
        }
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
          
            
                do {
                    if let error = error { throw error }
                    guard let data = data else { throw VersionError.invalidResponse }
                    
                    let result = try JSONDecoder().decode(LookupResult.self, from: data)
                    print(result.results)
                    guard let info = result.results.first else {
                        throw VersionError.invalidResponse
                    }

                    completion(info, nil)
                } catch {
                    completion(nil, error)
                }
            }
        
        task.resume()
        return task

    }

    func getBundle(key: String) -> String? {

        guard let filePath = Bundle.main.path(forResource: "Info", ofType: "plist") else {
          fatalError("Couldn't find file 'Info.plist'.")
        }
        // 2 - Add the file to a dictionary
        let plist = NSDictionary(contentsOfFile: filePath)
        // Check if the variable on plist exists
        guard let value = plist?.object(forKey: key) as? String else {
          fatalError("Couldn't find key '\(key)' in 'Info.plist'.")
        }
        return value
    }
}

extension UIViewController {
    @objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
        guard let appName = CheckUpdate.shared.getBundle(key: "CFBundleName") else { return } //Bundle.appName()

        let alertTitle = "New version"
        let alertMessage = "A new version of \(appName) are available on AppStore. Update now!"

        let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)

        if !Force {
            let notNowButton = UIAlertAction(title: "Not now", style: .default)
            alertController.addAction(notNowButton)
        }

        let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
            guard let url = URL(string: AppURL) else {
                return
            }
            if #available(iOS 10.0, *) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                UIApplication.shared.openURL(url)
            }
        }

        alertController.addAction(updateButton)
        self.present(alertController, animated: true, completion: nil)
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

How then can I declare a ViewController class and call a function in that class?
There are a few ways to do it. However I'll show you a simple way to do it for the sake of simplicity. Now I'll modify your code.
Thank you so much! It works, but there are a couple of questions. How can we change the value of a variable that is declared as let. I also added a multi-threaded function call.
Let declaration specifies a value as a constant. So a let declared variable is immutable; it cannot be modified. If you want a mutable value you must daclare it using var.
|
0

If your CheckUpdate class resides in a file other than ViewController's file this is not to correct way to do it. You can define a closure or protocol in ViewController then implement it in CheckUpdate class. Something like:

class ViewController: UIViewController {
   ...
   typeAlias OnUpdate = () -> Void
   ...
   override func viewDidLoad() {
      super.viewDidLoad()
      self.load_shop()
   }
   ...
   let checkupdate = CheckUpdate(){
      view = webView
      print("finish_update")
   }
}
class CheckUpdate: NSObject {
   ...
   var onupdate:OnUpdate?
   ...
   init(_ onupdate: OnUpdate?){
       self.onupdate = onupdate
   }
   ...
   if (...) {
      //ViewController().load_shop()
      onupdate?()
   }
}

9 Comments

CheckUpdate class is in the same class as ViewController
Have you tried to change load_shop funcion's access specifier as fileprivate and then call it from CheckUpdate class directly?
I don't think you understand me. The function is executed. The webView is not displayed.
Do you have enough knowledge about object oriented programming? Do you know what instantiation or instance of an object means?
Unfortunately not yet.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.