#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
? Вы можете создать метод initinit(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.
Ответ №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. К сожалению, пока нет.