我遇到了这样的问题:在使用 Action 方法启动 AlertController 后,我的应用程序似乎忘记了当前的视图控制器及其属性(即 tableView)。
例如,在通过按钮操作呈现警报后,当函数引用屏幕上的表格视图(它说为零)时,点击按钮会导致崩溃。
所讨论的 VC 嵌入在导航控制器中,并从作为 UITabBarController 一部分的 VC 以模态方式启动
所以堆栈应该类似于 UITabBarController -> UINavigationController -> ViewControllerOnScreen -> AlertController
我解决了之前的一个问题,即使用以下方法点击 UIGesture 识别器后 VC 从堆栈中消失(尽管仍在屏幕上_):
func topVC() -> UIViewController {
var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
在这种情况下,我只需要启动警报即可。之前方法中的 topVC 是屏幕上的 ViewController,它成功启动了警报。
但是,现在屏幕上的tableView为零。 tableView 是 VC 的一个属性,它被声明为 IBOutlet
@IBOutlet var tableView : UITableView!
但是,在这种情况下,当我调用上面的topVC方法时,topVC是navigationController而不是屏幕上的VC,因此它没有tableView属性。
有没有办法从navigationController引用我想要的VC(屏幕上的VC)?
非常感谢任何建议。
编辑:
根据 Karmen 的建议,提供的链接中有很多答案。
我将它们拼凑在一起,如下所示,但仍然没有获得包含属性的实际 VC。我刚刚得到 UINavigationController:
extension UIWindow {
func visibleController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
由于我有一个alertController,我怀疑按键窗口存在一些问题。链接中的 Objective-C 答案解决了 AlertController 的 keyWindow 问题,但我正在努力翻译成 Swift:
-(UIWindow *) returnWindowWithWindowLevelNormal
{
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *topWindow in windows)
{
if (topWindow.windowLevel == UIWindowLevelNormal)
return topWindow;
}
return [UIApplication sharedApplication].keyWindow;
}
-(UIViewController *) getTopMostController
{
UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
if (topWindow.windowLevel != UIWindowLevelNormal)
{
topWindow = [self returnWindowWithWindowLevelNormal];
}
UIViewController *topController = topWindow.rootViewController;
if(topController == nil)
{
topWindow = [UIApplication sharedApplication].delegate.window;
if (topWindow.windowLevel != UIWindowLevelNormal)
{
topWindow = [self returnWindowWithWindowLevelNormal];
}
topController = topWindow.rootViewController;
}
while(topController.presentedViewController)
{
topController = topController.presentedViewController;
}
if([topController isKindOfClass:[UINavigationController class]])
{
UINavigationController *nav = (UINavigationController*)topController;
topController = [nav.viewControllers lastObject];
while(topController.presentedViewController)
{
topController = topController.presentedViewController;
}
}
return topController;
}
这并不能回答你关于“寻找顶级风险投资家”的问题 - 因为我真的认为这不是你想要使用的方法。
相反,让我们看看向单元格类添加一个
Closure
,以便我们的控制器可以在点击单元格图像时“执行某些操作”。
首先,我们向单元类添加一个属性,如下所示:
class MyTestCell: UITableViewCell {
var imgTapClosure: ((UITableViewCell) -> ())?
然后,同样在单元类中,在我们处理点击手势的函数中:
@objc func handleTap(_ g: UITapGestureRecognizer) {
// call the Closure to inform the controller of the tap
imgTapClosure?(self)
}
在控制器的
cellForRowAt
函数中,我们可以“设置”该闭包:
// set the closure
cell.imgTapClosure = { [weak self] theCell in
guard let self = self,
let idxPath = self.tableView.indexPath(for: theCell)
else { return }
// now we know which cell got the tap on the image, and
// we can "do something" with that information
self.doSomething(indexPath: idxPath)
}
现在,在同一个控制器中,我们可以编写一个
doSomething()
函数来呈现一个 UIAlertController
。
如果具有表视图的控制器是“根”控制器,或者它位于导航堆栈中,或者如果它位于选项卡控制器中,或者如果它位于选项卡控制器的导航堆栈中,则这将起作用。
这是一个完整的例子...
简单的数据结构 - “lastAction”将由警报控制器设置
struct MyTestObject {
var title: String = ""
var imgName: String = ""
var lastAction: String = ""
}
Cell 类 - 带闭包
class MyTestCell: UITableViewCell {
var imgTapClosure: ((UITableViewCell) -> ())?
var titleLabel: UILabel = UILabel()
var lastActionLabel: UILabel = UILabel()
var theImageView: UIImageView = UIImageView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier);
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// for this example, we're going to use SF Symbol images
// which do some weird things with framing
// so let's embed it in a view
let cView = UIView()
cView.addSubview(theImageView)
contentView.addSubview(titleLabel)
contentView.addSubview(lastActionLabel)
contentView.addSubview(cView)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
lastActionLabel.translatesAutoresizingMaskIntoConstraints = false
cView.translatesAutoresizingMaskIntoConstraints = false
theImageView.translatesAutoresizingMaskIntoConstraints = false
let g = contentView.layoutMarginsGuide
// avoid auto-layout complaints
let b = cView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0)
b.priority = .required - 1
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
lastActionLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
lastActionLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
cView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
cView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
b,
cView.widthAnchor.constraint(equalToConstant: 60.0),
cView.heightAnchor.constraint(equalToConstant: 60.0),
theImageView.widthAnchor.constraint(equalToConstant: 60.0),
theImageView.heightAnchor.constraint(equalToConstant: 60.0),
theImageView.centerXAnchor.constraint(equalTo: cView.centerXAnchor, constant: 0.0),
theImageView.centerYAnchor.constraint(equalTo: cView.centerYAnchor, constant: 0.0),
])
// element properties
theImageView.contentMode = .scaleAspectFit
theImageView.tintColor = .systemRed
// add tap gesture
let t = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
theImageView.addGestureRecognizer(t)
theImageView.isUserInteractionEnabled = true
// so we can see the framing
titleLabel.backgroundColor = .yellow
lastActionLabel.backgroundColor = .init(red: 0.9, green: 0.95, blue: 1.0, alpha: 1.0)
theImageView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
cView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
}
@objc func handleTap(_ g: UITapGestureRecognizer) {
// call the Closure to inform the controller of the tap
imgTapClosure?(self)
}
}
视图控制器类 - 将表视图作为子视图...当点击单元格中的 imageView 时,我们将呈现一个带有几个选项的警报控制器。
class MyTestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var theData: [MyTestObject] = []
// if you have set this up in Storyboard
//@IBOutlet var tableView: UITableView!
// for this example, we'll add the table view via code
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
self.title = "Activities"
// create some sample data, using a bunch of
// SF Symbol names from the "Fitness" category
let imgNames: [String] = [
"figure.american.football",
"figure.archery",
"figure.australian.football",
"figure.badminton",
"figure.barre",
"figure.baseball",
"figure.basketball",
"figure.bowling",
"figure.boxing",
"figure.climbing",
"figure.cooldown",
"figure.core.training",
"figure.cricket",
"figure.skiing.crosscountry",
"figure.cross.training",
"figure.curling",
"figure.dance",
"figure.disc.sports",
"figure.skiing.downhill",
"figure.elliptical",
"figure.equestrian.sports",
"figure.fencing",
]
for (i, str) in imgNames.enumerated() {
let obj = MyTestObject(title: "Row \(i)", imgName: str)
theData.append(obj)
}
// if you're using an @IBOutlet table view, the following is not needed
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
tableView.register(MyTestCell.self, forCellReuseIdentifier: "c")
tableView.dataSource = self
tableView.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return theData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! MyTestCell
let d = theData[indexPath.row]
cell.titleLabel.text = d.title
cell.lastActionLabel.text = "Last Action: " + d.lastAction
if let img = UIImage(systemName: d.imgName) {
cell.theImageView.image = img
}
// set the closure
cell.imgTapClosure = { [weak self] theCell in
guard let self = self,
let idxPath = self.tableView.indexPath(for: theCell)
else { return }
// now we know which cell got the tap on the image, and
// we can "do something" with that information
self.doSomething(indexPath: idxPath)
}
return cell
}
func doSomething(indexPath: IndexPath) {
// use preferredStyle: .actionSheet or .alert as desired
//let alertController = UIAlertController(title: "My Title", message: "What do you want to do?", preferredStyle: .actionSheet)
let alertController = UIAlertController(title: "My Title", message: "What do you want to do?", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save the image", style: .default, handler: { (alertAction: UIAlertAction) in
// add code to Save the image
// for this example, let's just update the action and reload the row
self.theData[indexPath.row].lastAction = "Save!"
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}))
alertController.addAction(UIAlertAction(title: "Share the image", style: .default, handler: { (alertAction: UIAlertAction) in
// add code to Share the image
// for this example, let's just update the action and reload the row
self.theData[indexPath.row].lastAction = "Share!"
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}))
// only if we are in a navigationController stack,
// add a "Show" action
if let navVC = self.navigationController {
alertController.addAction(UIAlertAction(title: "Show the image", style: .default, handler: { (alertAction: UIAlertAction) in
// for this example, update the action and reload the row
self.theData[indexPath.row].lastAction = "Show!"
self.tableView.reloadRows(at: [indexPath], with: .none)
// then push to a new VC to show the image
let vc = ShowImageVC()
vc.theImageName = self.theData[indexPath.row].imgName
navVC.pushViewController(vc, animated: true)
}))
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (alertAction: UIAlertAction) in
// user selected Cancel, so
// for this example, let's just update the action and reload the row
self.theData[indexPath.row].lastAction = "Cancelled"
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}))
present(alertController, animated: true)
}
}
“显示图像”视图控制器类 - 如果在导航控制器中运行,我们将有一个选项“显示”图像,这将实例化并推送此控制器。
class ShowImageVC: UIViewController {
var theImageName: String = ""
let imgView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
self.title = "Show Image"
imgView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imgView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
imgView.contentMode = .scaleAspectFit
imgView.tintColor = .systemRed
if let img = UIImage(systemName: theImageName) {
imgView.image = img
}
}
}
一些屏幕截图,显示上面的代码在导航堆栈中的控制器中运行,在选项卡视图控制器中......
首次加载时:
点击第 2 行单元格中的图像:
选择“保存” - 我们更新数据并重新加载行(实际代码将保存图像):
点击单元格第 3 行中的图像,选择“共享” - 我们更新数据并重新加载该行(实际代码将共享图像):
点击单元格第 4 行中的图像,选择“取消” - 我们更新数据并重新加载该行:
点击第 5 行单元格中的图像,选择“显示” - 我们更新数据并重新加载该行,然后推送到新的 VC:
演出回来:
如果需要,我们可以将警报控制器设置为
preferredStyle: .actionSheet
而不是 .alert
: