Функция из другого класса ios swift не выполняется

#ios #swift

#iOS #swift

Вопрос:

Пожалуйста, скажите мне, у меня есть 2 класса. Существует load_shop() функция, которая позволяет отображать WebView. Если я вызываю функцию из первого класса ViewController , то функция выполняется и отображается WebView, а если из другого класса CheckUpdate , функция выполняется (выводится на печать), но WebView не отображается. В чем может быть проблема? Спасибо за любую помощь. Я новичок в этом.

 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
    }
}
 

Проверьте обновление.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)
    }
}
 

Комментарии:

1. ViewController() создает совершенно новый экземпляр, который не является тем экземпляром, который вы ожидаете, если используете storyboard. Вам нужна фактическая ссылка через переход или создание экземпляра. И еще раз, пожалуйста, назовите функции и переменные в нижнем регистре в соответствии с соглашением об именовании.

2. @vadian «Вам нужна фактическая ссылка через переход или создание экземпляра». Что вы имеете в виду? Можете ли вы показать это на примере?

3. Я только что увидел, что CheckUpdate это не другой контроллер представления. Как связан этот класс с ViewController ? Вы можете создать метод init init(controller : ViewController) в CheckUpdate и передать ссылку.

4. @vadian Я не думаю, что вы меня понимаете. Функция выполнена. Веб-просмотр не отображается.

5. Я не думаю, что вы меня понимаете . Функция выполняется в новом экземпляре. Но это не экземпляр в раскадровке, поэтому представления не связаны.

Ответ №1:

пусть first_class = ViewController() // прекратит это делать. Это не сработает. Подумайте об этом; у вас есть автомобиль (контроллер просмотра), вы хотите включить радио в своей машине. Что вам нужно было бы сделать? Сначала включите свой автомобиль, а затем его радио, верно? Но то, что вы делаете в своем коде, это; вместо того, чтобы включать радио в своей машине, вы идете и получаете новую машину (например, новый экземпляр viewcontroller) и включаете радио в новой машине. Но на самом деле вам нужно включить радио вашего собственного автомобиля, а не нового автомобиля. Так работают экземпляры в объектно-ориентированном программировании. Вы создали экземпляр UIViewController (это ваш автомобиль), затем в другом классе вы создаете другой экземпляр UIViewController (это не ваш автомобиль в примере). Затем вы пытаетесь изменить первую, но на самом деле вы изменяете другую. Как только я вернусь домой, я изменю ваш код со своего ноутбука, потому что его сложно редактировать с мобильного телефона.

Вот измененный код. ОБРАТИТЕ ВНИМАНИЕ, ЧТО я просто исправляю те части, в которых вы хотите вызвать функцию load_shop из класса CheckUpdate. Я имею в виду, что я не знаю всей логики вашего кода, так что, если он содержит больше логических ошибок, все зависит от вас.

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
    }
}
 

Проверьте обновление.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)
    }
}
 

Комментарии:

1. Как тогда я могу объявить класс ViewController и вызвать функцию в этом классе?

2. Есть несколько способов сделать это. Однако я покажу вам простой способ сделать это для простоты. Теперь я изменю ваш код.

3. Большое вам спасибо! Это работает, но есть пара вопросов. Как мы можем изменить значение переменной, которая объявлена как let . Я также добавил многопоточный вызов функции.

4. Объявление Let указывает значение как константу. Таким образом, объявленная переменная let является неизменяемой; она не может быть изменена. Если вам нужно изменяемое значение, вы должны указать его с помощью var.

5. picua.org/images/2020/11/21/…

Ответ №2:

Если ваш класс CheckUpdate находится в файле, отличном от файла ViewController, это неправильный способ сделать это. Вы можете определить замыкание или протокол в ViewController, а затем реализовать его в классе CheckUpdate. Что — то вроде:

 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?()
   }
}
 

Комментарии:

1. Класс CheckUpdate находится в том же классе, что и ViewController

2. Вы пытались изменить спецификатор доступа функции load_shop как fileprivate, а затем вызвать его непосредственно из класса CheckUpdate?

3. Я не думаю, что вы меня понимаете. Функция выполнена. WebView не отображается.

4. Достаточно ли у вас знаний об объектно-ориентированном программировании? Знаете ли вы, что означает создание экземпляра или экземпляра объекта?

5. К сожалению, пока нет.