Swift: 如何在应用程序打开时更改 UIImage
Swift: How to change UIImage when app is opening
我正在尝试按照 YouTube 教程并通过制作一个简单的纸牌游戏来学习 Swift。完成本教程后,我想添加一些我自己的功能,例如在应用程序打开后重置乐谱和卡片。当我说打开时,我的意思是进入前台或启动应用程序。我目前正在尝试通过在 ViewController.swift
中创建一个名为 reset
的方法,并在调用函数 applicationWillEnterForeground
时调用 AppDelegate.swift
中的方法。我能够成功构建代码并 运行 但是当应用程序进入前台时我收到一条错误消息 "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"。
AppDelegate.swift:
//
// AppDelegate.swift
// War
//
// Created by Rafael on 3/17/18.
// Copyright © 2018 Rafael. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate{
var window: UIWindow?
var ViewControl: ViewController = ViewController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
print("Launching app!")
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
print("Going inactive!")
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
print("Entering background!")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
print("Entering Foreground!")
ViewControl.reset()
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
print("Going active!")
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
print("Going to terminate!")
}
}
enter code here
ViewController.swift:
//
// ViewController.swift
// War
//
// Created by Rafael on 3/17/18.
// Copyright © 2018 Rafael. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var rightImageView: UIImageView!
@IBOutlet weak var leftImageView: UIImageView!
@IBOutlet weak var leftScoreLabel: UILabel!
var leftScore = 0
@IBOutlet weak var rightScoreLabel: UILabel!
var rightScore = 0
let cardNames = ["card2", "card3", "card4", "card5", "card6", "card7", "card8", "card9", "card10", "card11", "card12", "card13", "card14"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("loading view!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func dealTapped(_ sender: Any) {
let leftNumber = Int(arc4random_uniform(13))
let rightNumber = Int(arc4random_uniform(13))
if leftNumber > rightNumber {
leftScore += 1
leftScoreLabel.text = String(leftScore)
} else if(rightNumber == leftNumber){
// Do nothing - TIE
} else{
rightScore += 1
rightScoreLabel.text = String(rightScore)
}
leftImageView.image = UIImage(named: cardNames[leftNumber])
rightImageView.image = UIImage(named: cardNames[rightNumber])
}
func reset() {
print("resetting!")
leftImageView.image = UIImage(named: "back")
rightImageView.image = UIImage(named: "back")
rightScoreLabel.text = "0"
leftScoreLabel.text = "0"
rightScore = 0
leftScore = 0
}
}
所有网点工作正常,应用程序运行良好。唯一的问题是调用函数 reset
时。当我的应用程序将要出现时,如何让我的应用程序重置卡片?我还在学习语言。
你做错了几件事。首先,您似乎没有将任何数据保存到磁盘,因此每次启动时一切都会重置。但这里有几件事。
1:您应该在视图控制器本身内部重置 ViewController 的属性/状态;不是来自 AppDelegate。
2:你这样命名你的视图控制器变量。
var ViewController: ViewController = ViewController()
。在 Swift / Objective-C 中命名变量的正确方法是驼峰式 var viewController = ViewController()
或更好的方式 var myViewController = ViewController()
。因为当您输入 ViewController 时,它与 class 本身同名,它并没有尝试访问您的实例。将你的 ivars 命名为与 class 名称完全相同,这真的会让你自己(和编译器)感到困惑。
3:您初始化了 ViewController 的一个实例,但它与您在屏幕上看到的实例不同。您在屏幕上看到的内容由 Storyboard 拥有和创建。您在 AppDelegate 中创建的 ViewController 实际上并没有做任何事情。这就是 IBOutlets 为零的原因,因为屏幕上的标签位于 Storyboard 自己创建的实例内部。所以你所要做的就是把你的逻辑放在你的 ViewController.swift 文件中;您不必创建自己的实例。
话虽这么说,如果您希望视图控制器出现在屏幕上后发生某些事情,您要么需要在其中编写一个名为 override func viewWillAppear (animated: Bool) {}
的函数,要么将其放在 viewDidLoad() 中。在 iOS 中,使用了模型视图控制器模式。每个屏幕都由一个 UIViewController 控制。因此,任何一种用于操作屏幕及其视图的逻辑都应该在 View Controller subclass 内部完成。视图控制器的工作方式是,首先调用 viewDidLoad()
,然后调用 viewWillAppear()
,最后调用 viewDidAppear()
。
这里有几件事需要考虑。首先,每次他们离开应用程序并返回时尝试重置您的视图状态几乎肯定不是用户所期望的。你能想象在玩游戏时,收到一封电子邮件,然后你切换来看,然后返回,你的游戏又回到了关卡的开头吗?
此外,除非您正在做一些独立于视图控制器的事情,例如从后台获取云记录,否则您可能需要远离 AppDelegate。 Apple 为其平台设置的范例(模型视图控制器)是从视图控制器内部控制您在屏幕上看到的内容。您正在尝试从 AppDelegate 控制视图控制器以操纵屏幕上的内容。我建议重新考虑您将如何控制屏幕内容。此外,一旦该应用程序在后台运行了一段时间(您在主屏幕或其他应用程序中),它最终将重置其状态,然后将再次调用 viewWillAppear。这就是为什么每个 ViewController 都有在发生这种情况时保存和恢复状态的方法。
尽管如此,还是有一种方法可以从 AppDelegate 访问您的 ViewController 实例。您可以像这样 let rootVC = UIApplication.shared.keyWindow?.rootViewController
到达根视图控制器。但请记住,您认为的主屏幕可能不是根视图控制器。根控制器可以是选项卡栏或导航控制器。如果是这样,您将不得不继续沿着链条向下走,直到找到它。
无需直接访问其实例即可将消息传送到 ViewController 的另一种方法是使用基金会的 NotificationCenter 发送消息。像这样:NotificationCenter.default.post(name: NSNotification.Name(rawValue: "app has returned from background"), object: nil)
。然后在视图控制器中,你将放置如下内容:NotificationCenter.default.addObserver(self, selector: #selector(screenRefreshedFunction), name: NSNotification.Name(rawValue: "app has returned from background"), object: nil)
,然后创建该函数。我认为 iOS 有 built-in 应用程序进入后台时的通知,您可以设置为观察。如果不查看文档,我不确定它们是什么。
我正在尝试按照 YouTube 教程并通过制作一个简单的纸牌游戏来学习 Swift。完成本教程后,我想添加一些我自己的功能,例如在应用程序打开后重置乐谱和卡片。当我说打开时,我的意思是进入前台或启动应用程序。我目前正在尝试通过在 ViewController.swift
中创建一个名为 reset
的方法,并在调用函数 applicationWillEnterForeground
时调用 AppDelegate.swift
中的方法。我能够成功构建代码并 运行 但是当应用程序进入前台时我收到一条错误消息 "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"。
AppDelegate.swift:
//
// AppDelegate.swift
// War
//
// Created by Rafael on 3/17/18.
// Copyright © 2018 Rafael. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate{
var window: UIWindow?
var ViewControl: ViewController = ViewController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
print("Launching app!")
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
print("Going inactive!")
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
print("Entering background!")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
print("Entering Foreground!")
ViewControl.reset()
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
print("Going active!")
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
print("Going to terminate!")
}
}
enter code here
ViewController.swift:
//
// ViewController.swift
// War
//
// Created by Rafael on 3/17/18.
// Copyright © 2018 Rafael. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var rightImageView: UIImageView!
@IBOutlet weak var leftImageView: UIImageView!
@IBOutlet weak var leftScoreLabel: UILabel!
var leftScore = 0
@IBOutlet weak var rightScoreLabel: UILabel!
var rightScore = 0
let cardNames = ["card2", "card3", "card4", "card5", "card6", "card7", "card8", "card9", "card10", "card11", "card12", "card13", "card14"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("loading view!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func dealTapped(_ sender: Any) {
let leftNumber = Int(arc4random_uniform(13))
let rightNumber = Int(arc4random_uniform(13))
if leftNumber > rightNumber {
leftScore += 1
leftScoreLabel.text = String(leftScore)
} else if(rightNumber == leftNumber){
// Do nothing - TIE
} else{
rightScore += 1
rightScoreLabel.text = String(rightScore)
}
leftImageView.image = UIImage(named: cardNames[leftNumber])
rightImageView.image = UIImage(named: cardNames[rightNumber])
}
func reset() {
print("resetting!")
leftImageView.image = UIImage(named: "back")
rightImageView.image = UIImage(named: "back")
rightScoreLabel.text = "0"
leftScoreLabel.text = "0"
rightScore = 0
leftScore = 0
}
}
所有网点工作正常,应用程序运行良好。唯一的问题是调用函数 reset
时。当我的应用程序将要出现时,如何让我的应用程序重置卡片?我还在学习语言。
你做错了几件事。首先,您似乎没有将任何数据保存到磁盘,因此每次启动时一切都会重置。但这里有几件事。
1:您应该在视图控制器本身内部重置 ViewController 的属性/状态;不是来自 AppDelegate。
2:你这样命名你的视图控制器变量。
var ViewController: ViewController = ViewController()
。在 Swift / Objective-C 中命名变量的正确方法是驼峰式 var viewController = ViewController()
或更好的方式 var myViewController = ViewController()
。因为当您输入 ViewController 时,它与 class 本身同名,它并没有尝试访问您的实例。将你的 ivars 命名为与 class 名称完全相同,这真的会让你自己(和编译器)感到困惑。
3:您初始化了 ViewController 的一个实例,但它与您在屏幕上看到的实例不同。您在屏幕上看到的内容由 Storyboard 拥有和创建。您在 AppDelegate 中创建的 ViewController 实际上并没有做任何事情。这就是 IBOutlets 为零的原因,因为屏幕上的标签位于 Storyboard 自己创建的实例内部。所以你所要做的就是把你的逻辑放在你的 ViewController.swift 文件中;您不必创建自己的实例。
话虽这么说,如果您希望视图控制器出现在屏幕上后发生某些事情,您要么需要在其中编写一个名为 override func viewWillAppear (animated: Bool) {}
的函数,要么将其放在 viewDidLoad() 中。在 iOS 中,使用了模型视图控制器模式。每个屏幕都由一个 UIViewController 控制。因此,任何一种用于操作屏幕及其视图的逻辑都应该在 View Controller subclass 内部完成。视图控制器的工作方式是,首先调用 viewDidLoad()
,然后调用 viewWillAppear()
,最后调用 viewDidAppear()
。
这里有几件事需要考虑。首先,每次他们离开应用程序并返回时尝试重置您的视图状态几乎肯定不是用户所期望的。你能想象在玩游戏时,收到一封电子邮件,然后你切换来看,然后返回,你的游戏又回到了关卡的开头吗?
此外,除非您正在做一些独立于视图控制器的事情,例如从后台获取云记录,否则您可能需要远离 AppDelegate。 Apple 为其平台设置的范例(模型视图控制器)是从视图控制器内部控制您在屏幕上看到的内容。您正在尝试从 AppDelegate 控制视图控制器以操纵屏幕上的内容。我建议重新考虑您将如何控制屏幕内容。此外,一旦该应用程序在后台运行了一段时间(您在主屏幕或其他应用程序中),它最终将重置其状态,然后将再次调用 viewWillAppear。这就是为什么每个 ViewController 都有在发生这种情况时保存和恢复状态的方法。
尽管如此,还是有一种方法可以从 AppDelegate 访问您的 ViewController 实例。您可以像这样 let rootVC = UIApplication.shared.keyWindow?.rootViewController
到达根视图控制器。但请记住,您认为的主屏幕可能不是根视图控制器。根控制器可以是选项卡栏或导航控制器。如果是这样,您将不得不继续沿着链条向下走,直到找到它。
无需直接访问其实例即可将消息传送到 ViewController 的另一种方法是使用基金会的 NotificationCenter 发送消息。像这样:NotificationCenter.default.post(name: NSNotification.Name(rawValue: "app has returned from background"), object: nil)
。然后在视图控制器中,你将放置如下内容:NotificationCenter.default.addObserver(self, selector: #selector(screenRefreshedFunction), name: NSNotification.Name(rawValue: "app has returned from background"), object: nil)
,然后创建该函数。我认为 iOS 有 built-in 应用程序进入后台时的通知,您可以设置为观察。如果不查看文档,我不确定它们是什么。