How to identify the viewController a button has been added onto? - ios

I want to know what was the name of the viewController the the button was tapped. I've already looked into the answer provided here. But I believe won't work if I have different containerViews on the screen...where the viewController for each button may be different. Hence I need a different solution.
So I wrote this function to recursively look until it finds a UIViewController.
extension UIView{
func getTypeOfMyViewController() -> UIViewController.Type?{
if let _super = superview as? UIViewController{ // logically wrong!
return type(of:_super)
}else if let _super = superview{
return _super.getTypeOfMyViewController()
}else{
assertionFailure("everything should end up being on some viewController")
return nil
}
}
}
The only problem is this line:
if let _super = superview as? UIViewController{
It gives me the following warning:
Cast from 'UIView?' to unrelated type 'UIViewController' always fails
superview is a UIView and I don't know how to extract the 'viewController' which contains the 'view'.
Question1: So How can I do that?
Additionally I would like to use the getTypeOfMyViewController function as such:
extension UIButton{
open override var accessibilityLabel: String?{
get {
return "\(getTypeOfMyViewController.self): \(titleLabel?.text ?? "Null")"
}
set{
// nothing particular
}
}
}
I'm doing this because I want to create a unique identifier for all button taps in my logging system.
Question2: Does Swift offer any easier solution to this?

A view controller is not a view, so it can never be a superview. You have the right idea, but you're looking at the wrong hierarchy. What you want is not the view hierarchy but the responder chain.
Walk up the responder chain until you come to the view controller:
var r : UIResponder = theButton
repeat { r = r.next! } while !(r is UIViewController)
let vc = r as! UIViewController

Related

Update subviews of UIView from ViewController

I have a UIViewController that implements a custom UIView, so;
override func loadView() {
view = CustomView()
}
The custom view has a few lables and buttons and all the normal stuff, problem is in my viewController I have a request, and when that request is done, I'd like to update some of those lables/buttons.
Right now, in my CustomView, I have functions, such as;
func updateView() {
labelOne.isHidden = true
LabelTwo.isHidden = false
}
So I call the appropriate function from my viewController when the request is done.
This works, but it feels wrong, is there a neater way to update the subviews of my custom UIView, from my viewController? Should I maybe be using protocols or delegates?
One thing I've found quite neat in the past is passing the model directly to the custom view, then using didSet to trigger updates.
class CustomView: UIView {
let labelOne = UILabel()
let labelTwo = UILabel()
var object:CustomObject! {
didSet {
self.labelOne.text = object.name
self.labelTwo.text = object.description
}
}
...
}
This means in your UIViewController you can do the request and then pass the model straight to the custom view.
RequestHelper.getObject() { object in
self.customView.object = object
}
Obviously here I'm guessing at your request and object names but hopefully you get the idea.

Responder chain error

I want to translate this kind of Objective-C code to Swift:
- (UINavigationController *)targetNaviCtrl{
if(!_targetNaviCtrl){
UIResponder *target = self.nextResponder;
do {
target = target.nextResponder;
} while (![target isKindOfClass:UINavigationController.self] && target != nil);
_targetNaviCtrl = (UINavigationController *)target;
}
return _targetNaviCtrl;
}
// by iterate its next responder, so I can get access the target viewController to do something.
// This is often used across several hierarchies.
// A root B, B push C , C push D. I use A in D viewController.
I met some trouble.
code repo
Apple Doc: next
Declaration
var next: UIResponder? { get }
Return Value
The next object in the responder chain or nil if this is the last object in the chain.
I want to access the left tarBarController in the right viewController.
class ViewControllerEightLayer
// the right viewController.
class ViewControllerEightLayer: UIViewController {
override var next: UIResponder?{
get{
return super.next
}
}// the right viewController.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
var nextResponder = self.next! // Here I call it
}
Here I call it, here is error info:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
class LayerTableVC
// the center a little right UITableViewController.
class LayerTableVC: UITableViewController {
override var next: UIResponder?{
get{
return super.next
}
}// the center a little right UITableViewController.
class LayerNavigationVC
// the center a little left UINavigationController.
class LayerNavigationVC: UINavigationController {
override var next: UIResponder?{
get{
return super.next
}
}// the center a little left UINavigationController.
class MainTabBarVC
// the left tarBarController
class MainTabBarVC: UINavigationController {
override var next: UIResponder?{
get{
return self
}
}// the left tarBarController
I do not know how to solve it in this way. Any suggestions?
Maybe It helps,
self.next?.target(forAction: <#T##Selector#>, withSender: <#T##Any?#>)
It seems wired.
I did it by the code ,
let tabBarViewController = UIApplication.shared.keyWindow!.rootViewController as! UITabBarController
And I want to know the responder chain solution
PS:
aim of digging through the responder chain is curiosity.
It happened , I do not think it will happen .
So I wonder why.
I'd like to reverse-engineering.
PPS:
My intend is to control the tool bar to hide and show. Not providing data from to
extension ViewControllerEightLayer: UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.text == "1" {
tabBarViewController.tabBar.isHidden = false
}
else{
tabBarViewController.tabBar.isHidden = true
}
return true
}
This is a personal experiment project, not of my corp.
You seem to be asking for how to walk up the responder chain. Here's how.
func showResponderChain(_ r: UIResponder) {
var r : UIResponder! = r
repeat { print(r, "\n"); r = r.next } while r != nil
}

Cannot assign value of type ViewSecond to ViewFirst

I try to assign UIView super class object's to other same type class object, getting error my code is below:
class ViewTest: UIViewController {
#IBOutlet var txtName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
var first = ViewFirst()
var second = ViewSecond()
func c() {
first = second
let datePicker = UIDatePicker()
txtName.inputView = datePicker
}
}
}
class ViewFirst: UIView {
}
class ViewSecond: UIView {
}
Why needed to type cast but txtName.inputView = datePicker working without any type cast
Thanks
This is wrong syntax. ViewSecond is not subclass or superclass to ViewFirst,even if you force cast the object like this:
first = second as! ViewFirst
you still get crash error.
So if ViewSecond is subclass to ViewFirst, just do force cast:
first = second as! ViewFirst
there will be well.
How cound I reply directlly? I am new...
Why txtName.inputView = datePicker without any cast type?
Because UIDatePicker is subClass of UIView! And then, the UIView is just a type define. So you have no need to do cast. For example:
func giveMeSomeView(view: UIView)
You can put it like UIButton, UIImageView.... But in the fuction, you have no idea which the exactlly view-Type, right? You just addSubview. Of course if you want to set something, you can have a implicit cast:
func giveMeSomeView(view: UIView) {
if let imageView = view as? UIImageView {
imageView.image = UIImage(string:"XXXX")
}
}
If there some word you could not understand, forgive it. My English is bad.
You should be more precise, do you have an error? Or which line doesn't work?
I guess the error is on this line :
first = second
You have to cast the object :
first = second as! ViewFirst
Please be more specific.
You can solve this problem bij making ViewSecond a subclass of ViewFirst instead of UIView.
For example:
class ViewFirst: UIView {
}
class ViewSecond: ViewFirst {
}

Weird underlying gray outlined view trying to dismiss programmatically the master of UISplitViewController

I use a UISplitViewController with preferredDisplayMode = UISplitViewControllerDisplayModePrimaryOverlay and I was looking for a way to dismiss master view controller. My master contains a table view and I'd like to close it whenever I select a cell. Surprisingly UISplitViewController doesn't seem to offer a method for that (but I do see Apple Mail doing that we you select an email in portrait mode).
I found the following workaround reported here: Hiding the master view controller with UISplitViewController in iOS8 (look at phatmann answer). This works but it also creates a weird animation when it's dismissed, there's an underlying gray outlined view which is not animated together with my master's view. The problem has been reported also here: iOS Swift 2 UISplitViewController opens detail screen on master's place when on iPad/ iPhone 6+
The problem occurs only when I dismiss master with this workaround, not when I tap on the secondary so I guess UISplitViewController is not following the regular dismiss flow when you just call sendAction on the button.
I used the following code to address this problem. There might be a better way to match on the specific view that is causing the issue. That said, this code was in an app approved by Apple in April of this year. What the code does is look for a specific view of a certain type, and if found, then it makes it hidden until the animation is complete. Its somewhat future proof, since if it does't detect the special view, it does nothing. I also added some comments for adopters on where you might want to make changes.
func closePrimaryIfOpen(finalClosure fc: (() -> Void)? = nil) {
guard let
primaryNavController = viewControllers[0] as? MySpecialNavSubclass,
primaryVC = primaryNavController.topViewController as? MySpecialCalss
else { fatalError("NO Special Class?") }
// no "official" way to know if its open or not.
// The view could keep track of didAppear and willDisappear, but those are not reliable
let isOpen = primaryVC.view.frame.origin.x >= -10 // -10 because could be some slight offset when presented
if isOpen {
func findChromeViewInView(theView: UIView) -> UIView? {
var foundChrome = false
var view: UIView! = theView
var popView: UIView!
repeat {
// Mirror may bring in a lot of overhead, could use NSStringFromClass
// Also, don't match on the full class name! For sure Apple won't like that!
//print("View: ", Mirror(reflecting: view).subjectType, " frame: \(view.frame)")
if Mirror(reflecting: view).description.containsString("Popover") { // _UIPopoverView
for v in view.subviews {
//print("SV: ", Mirror(reflecting: v).subjectType, " frame: \(v.frame)")
if Mirror(reflecting: v).description.containsString("Chrome") {
foundChrome = true
popView = v
//popView.hidden = true
break
}
}
if foundChrome { break }
}
view = view.superview
} while view != nil
return popView
}
// Note: leave it as optional - Apple changes things and we don't find the view, things still work!
let chromeView = findChromeViewInView(self.view)
UIView.animateWithDuration(0.250, animations: {
chromeView?.hidden = true
self.preferredDisplayMode = .PrimaryHidden
}, completion: { Bool in
self.preferredDisplayMode = .PrimaryOverlay
chromeView?.hidden = false
if let finalClosure = fc {
finalClosure()
}
//print("SLIDER CLOSED DONE!!!")
} )
}
}

Crash while adding UIImage from code

I'm a Swift beginner and I'm writing an app that will show a list of things. When you click on one of them, you'll get detailed information about it and a photo. However, I've got one problem - I've got code to show the image, but when I click in simulator it crashes.
Text that is on the bottom of xcode when the app crashes:
fatal error: unexpectedly found nil while unwrapping an Optional value
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var detail = segue.destinationViewController as! detailSkinsViewController
detail.skinNameLabel = self.skin
if (self.skin == "AK47 | Wasteland Rebel") {
detail.skinImg.image = UIImage(named: "s495fn")
}
}
Thanks!
You should look at the crash stack to see the actual line. There are a couple of places you could have trouble:
var detail = segue.destinationViewController as! detailSkinsViewController
This requests a crash if destinationViewController is not of class detailSkinsViewController. (Swift classes should always begin with a capital letter. This should also be a let, not var. You never modify it.) Using if-let here would be much safer.
detail.skinImg.image = UIImage(named: "s495fn")
It's very unclear what these are, but if skinImg is a UIImageView!, then you would expect this to crash if it the destination NIB has not been loaded yet (which is likely). You generally should never reach into other objects IBOutlets for exactly this reason. (This is also a good reason to use ? rather than ! for IBOutlets. That would lead to just "nothing happens" rather than a crash.)
Rather than messing with another view controllers outlets, you should create a UIImage? property on the view controller itself. In its didSet, update the UIImageView if the view is loaded (isViewLoaded). During viewDidLoad, initialize the UIImageView using the property. This way, you have a clear API for others to set the image that doesn't expose your internal subviews (which are implementation details).
As an example:
class ViewController: UIViewController {
#IBOutlet private var imageView: UIImageView?
var image: UIImage? {
didSet(newImage) {
self.imageView?.image = newImage
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.imageView?.image = self.image
}
}
You should check your optional values,
i did not see your skin variable definition but i think that code below will solve your problem
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detail = segue.destinationViewController as! detailSkinsViewController {
detail.skinNameLabel = self.skin
if (self.skin == "AK47 | Wasteland Rebel") {
detail.skinImg.image = UIImage(named: "s495fn")
}
}
}
This Error comes normally when trying to unwrap a value of an optional variable which has no value at all.
First check your optional variables and debug line by line to see if any of the Optional variables has no value at all which you had been trying to unwrap

Resources