如何在同一个 ViewController 中初始化两个 NSObject 实例 - Swift
Posted
技术标签:
【中文标题】如何在同一个 ViewController 中初始化两个 NSObject 实例 - Swift【英文标题】:How Do I Initialize Two Instances of NSObject in the same ViewController - Swift 【发布时间】:2015-07-30 15:47:27 【问题描述】:请多多包涵。我是编程新手,也是 *** 新手。希望我的提问能给我一个进入编程社区的热情回应。一位我相信其意见的熟人告诉我在 *** 上将这封电子邮件发给他。
我需要做什么才能让 NSObject 的两个实例在同一个 ViewController 中工作?我已经初始化了一个名为 SideBar 和 RightSideBar 的 NSObject 子类。它们都来自 NSObject。菜单中的单元格由我以编程方式创建的 TableViewController 创建。我遵循了一个以编程方式完成所有事情的教程,因为我不知道 Storyboard 更适合构建东西。
下面是代码库。
已编辑:抱歉啰嗦了。我不知道如何让它更短更完整
============
****注意 SideBar 子类是左侧菜单。 RightSideBar 类具有相同的初始化设置并且是右侧菜单。如果可能的话,我希望能够使它们都出现在同一个 ViewController 的同一个实例中的同一个 ViewController 上。****
这是左边的TableViewController:
import UIKit
//this protocol of the sidebar delegate manages the selection of the item in the row.
protocol SidebarTableViewControllerDelegate
func sidebarControlDidSelectRow(indexPath: NSIndexPath)
class SidebarTableViewController: UITableViewController
//setting up the delegate and array of menu items.
var delegate:SidebarTableViewControllerDelegate?
var tableData:Array <String> = []
var imageData:[UIImage] = []
// MARK: - Table view data source
//Setting up the number of sections in the menu
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
return 1
//Setting up the number of items in the menu
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return tableData.count
//Setting up the menu look for the main screen after login.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if cell == nil
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//configure the cell...
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame: CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
cell!.textLabel!.text = tableData[indexPath.row]
cell!.imageView!.image = imageData[indexPath.row]
return cell!
//Setting up the height for each cell of the table
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
return 45.0
//Setting up the selection of the item in the cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
delegate?.sidebarControlDidSelectRow(indexPath)
override func viewDidLoad()
override func didReceiveMemoryWarning()
这是正确的表格视图控制器:
//setting up the RightSidebarControllerDelegate
protocol RightSidebarTableViewControllerDelegate
func rightSidebarControlDidSelectRow(indexPath: NSIndexPath)
class RightSidebarTableViewController: UITableViewController
//setting up the delegate and array of menu items.
var delegate:RightSidebarTableViewControllerDelegate?
var rightTableData:Array <String> = []
var rightImageData:[UIImage] = []
// MARK: - Table view data source
//Setting up the number of sections in the menu
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
return 1
//Setting up the number of items in the menu
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return rightTableData.count
//Setting up the menu look for the main screen after login.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if cell == nil
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//configure the cell...
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame: CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
cell!.textLabel!.text = rightTableData[indexPath.row]
cell!.imageView!.image = rightImageData[indexPath.row]
return cell!
//Setting up the height for each cell of the table
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
return 45.0
//Setting up the selection of the item in the cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
delegate?.rightSidebarControlDidSelectRow(indexPath)
override func viewDidLoad()
override func didReceiveMemoryWarning()
这就是我的问题可能从 SideBar:NSObject 开始的地方。这是要初始化的左侧 SideBar:
import UIKit
@objc protocol SideBarDelegate
func sideBarDidSelectButtonAtIndex (index: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
optional func sideBarWillDeinitialize()
//this class sets up the actual sidebar.
class SideBar: NSObject, SidebarTableViewControllerDelegate
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let sideBarTableViewTopInset:CGFloat = 25.0
let sideBarContainerView:UIView = UIView()
let sideBarTableViewController:SidebarTableViewController = SidebarTableViewController()
var originView:UIView!
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
var delegate:SideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
override init()
super.init()
//initializer for the tableView of menu items.
init(sourceView: UIView, menuItems: Array<String>, menuImages: [UIImage])
super.init()
//initializing the views and animation for the menu.
originView = sourceView
sideBarTableViewController.tableData = menuItems
sideBarTableViewController.imageData = menuImages
setupSideBar()
animator = UIDynamicAnimator(referenceView: originView)
//function for setting up the sidebar.
func setupSideBar ()
//setting up the frame/outline of the side bar.
sideBarContainerView.frame = CGRectMake(-barWidth, originView.frame.origin.y + 45, barWidth, originView.frame.size.height)
//setting up the color of the sidebar.
sideBarContainerView.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
sideBarContainerView.clipsToBounds = false
//placing the sidebar in the UIView
originView.addSubview(sideBarContainerView)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = sideBarContainerView.bounds
sideBarContainerView.addSubview(blurView)
//setting up controls for the sidebar
sideBarTableViewController.delegate = self
sideBarTableViewController.tableView.frame = sideBarContainerView.bounds
sideBarTableViewController.tableView.clipsToBounds = false
//disabling the scroll feature. Delete to keep the scroll feature.
sideBarTableViewController.tableView.scrollsToTop = false
//This will remove separators in the UITableCell. Delete to keep separators.
sideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
//This sets the background color of the sidebar and creates the inset.
sideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
sideBarTableViewController.tableView.contentInset = UIEdgeInsets(top: sideBarTableViewTopInset, left: 0, bottom: 0, right: 0)
//reloads the sidebar and adds the container view to the sideBarTableViewController.
sideBarTableViewController.tableView.reloadData()
sideBarContainerView.addSubview(sideBarTableViewController.tableView)
func showSideBar(shouldOpen: Bool)
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//simple if and else statements to define the direction of animation and intensity of animation
let gravityX:CGFloat = (shouldOpen) ? 0.5 : -0.5
let magnitude:CGFloat = (shouldOpen) ? 20 : -20
let boundaryX:CGFloat = (shouldOpen) ? barWidth : -barWidth
//controls the behavior of the animation.
let gravityBehavior: UIGravityBehavior = UIGravityBehavior(items: [sideBarContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior: UICollisionBehavior = UICollisionBehavior(items: [sideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, originView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [sideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [sideBarContainerView])
sideBarBehavior.elasticity = 0.3
animator.addBehavior(sideBarBehavior)
func sidebarControlDidSelectRow(indexPath: NSIndexPath)
delegate?.sideBarDidSelectButtonAtIndex(indexPath.row)
这是最终将初始化右侧菜单的右侧 SideBar:NSObject。
import UIKit
@objc protocol RightSideBarDelegate
func rightSideBarDidSelectButtonAtIndex (index: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
class RightSideBar: NSObject, RightSidebarTableViewControllerDelegate
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let rightSideBarTableViewTopInset:CGFloat = 25.0
let rightSideBarContainerView:UIView = UIView()
let rightSideBarTableViewController:RightSidebarTableViewController = RightSidebarTableViewController()
var rightOriginView:UIView!
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
var delegate:RightSideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
override init()
super.init()
//initializer for the tableView of menu items.
init(rightSourceView: UIView, rightMenuItems: Array<String>, rightMenuImages: [UIImage])
super.init()
//initializing the views and animation for the menu.
rightOriginView = rightSourceView
rightSideBarTableViewController.rightTableData = rightMenuItems
rightSideBarTableViewController.rightImageData = rightMenuImages
setupSideBar()
animator = UIDynamicAnimator(referenceView: rightOriginView)
//function for setting up the sidebar.
func setupSideBar ()
//setting up the frame/outline of the side bar.
rightSideBarContainerView.frame = CGRectMake(rightOriginView.frame.size.width + barWidth , rightOriginView.frame.origin.y + 45, barWidth, rightOriginView.frame.size.height)
//setting up the color of the sidebar.
rightSideBarContainerView.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
rightSideBarContainerView.clipsToBounds = false
//placing the sidebar in the UIView
rightOriginView.addSubview(rightSideBarContainerView)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = rightSideBarContainerView.bounds
rightSideBarContainerView.addSubview(blurView)
//setting up controls for the sidebar
rightSideBarTableViewController.delegate = self
rightSideBarTableViewController.tableView.frame = rightSideBarContainerView.bounds
rightSideBarTableViewController.tableView.clipsToBounds = false
//disabling the scroll feature. Delete to keep the scroll feature.
rightSideBarTableViewController.tableView.scrollsToTop = false
//This will remove separators in the UITableCell. Delete to keep separators.
rightSideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
//This sets the background color of the sidebar and creates the inset.
rightSideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
rightSideBarTableViewController.tableView.contentInset = UIEdgeInsets(top: rightSideBarTableViewTopInset, left: 0, bottom: 0, right: 0)
//reloads the sidebar and adds the container view to the rightSideBarTableViewController.
rightSideBarTableViewController.tableView.reloadData()
rightSideBarContainerView.addSubview(rightSideBarTableViewController.tableView)
func showSideBar(shouldOpen: Bool)
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//simple if and else statements to define the direction of animation and intensity of animation
let gravityX:CGFloat = (shouldOpen) ? -0.5 : 0.5
let magnitude:CGFloat = (shouldOpen) ? -20 : 20
let boundaryX:CGFloat = (shouldOpen) ? -barWidth : barWidth
//controls the behavior of the animation.
let gravityBehavior: UIGravityBehavior = UIGravityBehavior(items: [rightSideBarContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior: UICollisionBehavior = UICollisionBehavior(items: [rightSideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, rightOriginView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [rightSideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [rightSideBarContainerView])
sideBarBehavior.elasticity = 0.3
animator.addBehavior(sideBarBehavior)
func rightSidebarControlDidSelectRow(indexPath: NSIndexPath)
delegate?.rightSideBarDidSelectButtonAtIndex(indexPath.row)
最后,这是我当前的 DoubleMenuViewController 代码。当我继续使用 DoubleMenuViewController 来破坏菜单时,会发生一些事情。菜单甚至不会加载。但是,如果我在一个只调用 SideBar:NSObject 的 SingleMenuViewController 中,那么只要我只调用一个菜单,代码就可以工作。在此 DoubleMenuViewController 中,我将 RightSideBar 类的初始化部分注释掉,因为我正在研究解决方案。我知道这个 ViewController 的代码是乱码。我正在尝试我能想到的一切。在代码后查看我的评论以查看我尝试过的内容:
import UIKit
class DoubleMenuViewController: UIViewController, SideBarDelegate, RightSideBarDelegate
var sideBar:SideBar?
var ondemandSideBar:SideBar
get
if sideBar == nil
//setting up the menu items for the sidebar.
sideBar = SideBar(sourceView: self.view, menuItems: ["Home", "Share", "About", "Help"], menuImages: [homeImage!, shareImage!, aboutImage!, helpImage!])
sideBar!.delegate = self
SideBar.new()
return sideBar!
//initializes the "RightSideBar"
var rightSideBar:RightSideBar?
var ondemandRightSideBar:RightSideBar
get
if rightSideBar == nil
rightSideBar = RightSideBar(rightSourceView: self.view, rightMenuItems: [//Other items], rightMenuImages: [//Other Items])
rightSideBar!.delegate = self
RightSideBar.new()
return rightSideBar!
var homeImage = UIImage(named: "Home")
var shareImage = UIImage(named: "Share")
var aboutImage = UIImage(named: "About")
var helpImage = UIImage(named: "Help")
@IBOutlet weak var currentMenuControl: UIBarButtonItem!
@IBAction func currentMenuDisplay(sender: AnyObject)
if currentMenuControl.tag == 1
ondemandSideBar.showSideBar(true)
currentMenuControl.tag = 0
else
ondemandSideBar.showSideBar(false)
currentMenuControl.tag = 1
@IBOutlet weak var progressionMenuControl: UIBarButtonItem!
@IBAction func progressionMenuDisplay(sender: AnyObject)
if progressionMenuControl.tag == 1
ondemandRightSideBar.showSideBar(true)
progressionMenuControl.tag = 0
else
ondemandRightSideBar.showSideBar(false)
progressionMenuControl.tag = 1
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view.
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
func sideBarDidSelectButtonAtIndex(index: Int)
switch index
//segues
func rightSideBarDidSelectButtonAtIndex(index: Int)
switch index
//segues
这是我尝试过的:
我尝试改变 CGFloats 的位置,因为 SubViews 似乎 从左边来。
我已重命名所有 RightSideBar 变量和类名 克服实例变量中的运行时混淆和 类名。这包括重命名您在 NSObject 子类和目标视图控制器。
我尝试在 viewDidLoad 方法中使用控制流 按钮标签。我取消了滑动功能以显示菜单和 添加按钮,因为我认为系统正在努力处理 滑动。
我尝试在 NSObject 的 SideBar 子类文件中取消初始化。 让我失望的只是一个导致应用程序崩溃的无限循环 登录后。
然后我尝试了按需初始化 targetViewController .....DoubleMenuViewController 和 单菜单视图控制器。我返回到带有按钮的工作菜单 在 SingleMenuViewController 但它仍然不会显示左侧和 DoubleMenuViewController 中的右侧菜单。
最后我尝试在 DoubleMenuViewController 中取消初始化 SideBar(左侧 SideBar)和 RightSideBar。但是,当我将 println() 函数添加到我的所有部分时,调试器不会为我运行打印函数来获取对象的值,甚至不会显示像 "This" 这样的类型化状态。我添加了打印功能,因为我不确定是否会知道取消初始化和重新初始化发生的时间。
看来我的菜单是从 SideBar: NSObject 文件和 RightSideBar:NSObject 文件初始化的。我的意思是我的菜单是在我点击目标视图控制器之前创建的。这对我来说不是问题,只要我可以让编译器在同一个 View Controller 中初始化 SideBar 和 RightSideBar,但它不会这样做。
我只需要能够通过滑动或点击按钮来控制这两个菜单。
我认为我的初始化程序相互覆盖有问题。
但是,我不知道如何解决这个问题。我已经阅读了 Swift 手册并阅读了互联网上的文章。我也搜索过 ***。
【问题讨论】:
这是一个很难回答的问题,因为它同时过于冗长(无意冒犯),但也没有足够的信息来重现问题(例如,我看不到这些菜单添加到视图层次结构)。您必须更简洁并提供MCVE。它也有错误的代码 sn-ps,但很难判断这些是原始代码中的问题(不可能,因为我看不到它可能如何编译)还是在创建问题。 你在哪里引用ondemandSideBar
和ondemandRightSideBar
,它们懒惰地实例化那些SideBar
和RightSideBar
对象?是什么触发了这些对象的实例化?你看到他们被召唤了吗?
很可能与手头的问题无关: 1. 顺便说一句,不要在 Swift 中调用 new
方法。 2. 你的委托对象在这里有很强的引用循环。 3.如果左右这些菜单有自己的视图控制器,则需要在将它们的视图添加到视图层次结构之前实现自定义容器视图控制器调用(例如addChildViewController
等)(否则视图层次结构和视图控制器层次结构是会失去同步。
Rob,我认为我的代码没有按照代码中的 cmets 所说的那样做。忽略代码中的 cmets。 Sidebar
和RightSidebar
的内容在DoubleMenuViewController
中实现。我认为我输入初始化值的sideBar = SideBar(//values here)
是初始化SideBar
的原因。 OP 说,当我尝试打印在 DoubleMenuViewController
中创建变量的位置时,甚至在使用标签的按钮函数内部显示没有任何注册。我不明白您第三条评论中的第二点和第三点。
是的,SideBar(...)
是您实例化它所需的全部内容(丢失new()
)。但是您在计算属性中调用它,因此如果您从不引用该属性,则不会实例化条形图。关于第 3 点,请参阅custom view controller containers 上的 WWDC 视频。
【参考方案1】:
你问:
如何在同一个视图控制器中初始化
NSObject
的两个实例?
抛开你为什么要处理NSObject
(在Objective-C中所有类最终都必须从NSObject
子类化,在Swift中不再是这种情况),如果你想实例化两个对象,你只需要每个属性都有一个。
如果这些是延迟实例化的,正如您的代码 sn-p 建议的那样,那么您必须确定引用该延迟实例化的属性的位置(例如,您可能会从“从边缘滑动”手势触发它)或您有什么。在引用该延迟实例化属性的代码中设置一个断点,并确保您完全到达那里。
--
我对您的一个代码 sn-ps 有一些观察。你说你正在像这样实例化你的侧边栏:
var sideBar : SideBar?
var ondemandSideBar : SideBar
get
if sideBar == nil
sideBar = SideBar(sourceView, menuItems, menuImage etc.)
sideBar!.delegate
SideBar.new()
我认为这不是你真正要做的,因为你没有设置委托,你既实例化了 SideBar
又调用了 new
(你不应该从 Swift ),你没有返回一个值,等等。
此外,存储属性由某个计算属性实例化的模式具有一定的 Objective-C je ne sais quoi。 我推断您想要一个延迟实例化的属性。如果是这种情况,我会倾向于使用单个 lazy
存储属性。然后我会使用闭包懒惰地设置该属性:
我希望有这样的模式
protocol SideBarDelegate : class // specify class so we can use `weak` reference
func didChooseMenuItem(sender: SideBar, item: Int)
class SideBar
weak var delegate: SideBarDelegate? // make sure this is weak to avoid strong reference cycle
class ViewController: UIViewController, SideBarDelegate
lazy var sideBar: SideBar =
let _sideBar = SideBar(sourceView, menuItems, menuImage, etc.)
_sideBar.delegate = self
return _sideBar
()
func didChooseMenuItem(sender: SideBar, item: Int)
// handle it here
// etc.
这样,sideBar
不会被实例化,直到您在代码中的某处引用 sideBar
,但当您这样做时,它将使用该闭包内的代码实例化。
【讨论】:
我已完成添加您的解决方案。你的回答让我回到了我以前曾经到达过的位置:RightSideBar
将初始化并显示,但是当你向左滑动以显示RightSideBar
时,它会一直弹到DoubleMenuViewController
的左侧并表现以左侧菜单/SideBar
的方式在向左滑动之后。然后你必须向右滑动才能打开正确的菜单,而不是像你应该的那样向左滑动。如果您尚未在DoubleMenuViewController
中向任何方向滑动,则左侧菜单/SideBar
将根本不会打开。左侧菜单无法启动
关于视图控制器点,我遵循的教程以编程方式完成了所有工作。所以,我没有视图控制器。我不能将UITableViewController
称为SideBar
或RightSideBar
的子ViewController。同样,由于我将左右菜单 SideBar
和RightSideBar
声明为 NSObject 的子类,因此我无法将 SideBar
或 RightSideBar
作为 childViewControllers 添加到 originView
变量中,该变量在 DoubleMenuViewController
中初始化为 sourceView
。在初始化之前在 viewDidLoad 方法中尝试self.addChildViewController(SideBar)
会产生错误
在视图控制器包含的东西上,在你调用containerViewController.view.addSubview(sideBarViewController.tableView)
的地方,你也应该调用containerViewController.addChildViewController(sideBarViewController)
,当动画完成后你应该调用sideBarViewController.didMoveToParentViewController(containerViewController)
。
你的动画问题,简化问题。是不是正确的那个不工作?如果暂时去掉左边的,右边的还不行吗?
右侧菜单将与您发布的代码一起使用,但它具有您期望从左侧菜单中获得的滑动行为。左侧菜单根本不起作用。明天我会移除左侧菜单,看看行为是否会发生变化。以上是关于如何在同一个 ViewController 中初始化两个 NSObject 实例 - Swift的主要内容,如果未能解决你的问题,请参考以下文章
如何有返回按钮才能返回到第一个 ViewController?