EXC_BAD_INSTRUCTION with Swift UIButton - swift

I am using below code in a function to handle all UIButtons in my View and it works fine when all the objects in the View are UIButton.
for v in self.mainView.subviews as [UIButton]
{
println (v.tag)
}
But in case there is any other objects like Label or ImageView in the same View, I get error 'EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subdued=0x0)'.
How should I change the code so that it works only for (all) UIButton.

The subview property of UIView is an array of AnyObject. The runtime happens because you are performing a forced downcast from [AnyObject] to [UIButton].
From a compiler perspective, the down cast is legit:
let x: [UIButton] = [AnyObject]() as [UIButton]
because AnyObject can be any class, and UIButton is a class.
In your case you are making the assumption that all objects contained in subviews are instances of UIButton, which can be possible (if you explicitly add UIButtons only to the view), but if the view has other UI elements (labels, other views, etc.) then the downcast will fail at runtime.
If for example the view contains another view, the above downcast is equivalent to doing this:
var view = UIView()
var button = view as UIButton
which fails because a UIView is not a UIButton (although thanks to polymorphism the opposite is true, being UIButton inherited from UIView).
If you want your code to print the tag for all UIButtons, ignoring all other UI elements, then #rahul_send89's answer is the correct one: it loops through all elements of the subview property, and it prints the tag only if the current element is a UIButton.
#SteveRosenberg's answer instead print the tag for all elements, regardless of their actual type.
Depending on what you want to do with your buttons (I presume the code posted in your question is just placeholder to explain the problem), there's an alternate way: filtering all buttons from the subviews property and storing into an array:
var buttons = mainView.subviews.filter { $0 is UIButton } as [UIButton]
and do whatever you need with this array of UIButton, such as printing their tag.
for button in buttons {
println(button.tag)
}

This worked for me:
for v in self.view.subviews as [AnyObject]
{
println (v.tag)
}

for v in self.mainView.subviews as [AnyObject]
{
if v is UIButton{
println (v.tag)
}
}

Related

Different behavior between addTarget and addGestureRecognizer

I have a function that creates a button with a selector function as a target. The address of a button gets passed to handleSelectPhoto.
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto))
func createButton(selector: Selector) -> UIButton {
let button = UIButton(type: .system)
button.addTarget(self, action: selector, for: .touchUpInside)
return button
}
#objc func handleSelectPhoto(button: UIButton) {
// Do something with button, this works
}
Now, I am trying to change the class of the above from UIButton to UIImageView like the following,
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto))
func createButton(selector: Selector) -> UIImageView {
let view = UIImageView()
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: selector))
view.isUserInteractionEnabled = true
return view
}
#objc func handleSelectPhoto(button: UIImageView) {
// HERE, button does not get passed
}
With the above changes, in handleSelectPhoto, button instance is not correct. I can not read it as UIImageView type.
If I add a selector function using addGestureRecognizer, does it behave differently than adding a selector function using addTarget, in terms of how selector function is executed with parameters? Maybe I am not understanding how this selector function works...
Adding a target to something like UIGestureRecognizer or UIButton only passes one parameter to the selected function. This parameter depends on the type you are about to add the target on.
In your case the first code snippet works because you are adding a target to an UIButton, so your selected function gets passed this UIButton instance.
In your second scenario you add the target to an UITapGestureRecognizer, so the passed instance will be exactly this gesture recognizer, which cannot be of type UIImageView.
So the difference from the target parameter perspective between UIGestureRecognizer and UIButton is no difference. They both pass their instances to the selected function.
From the UIView subclass perspective there is the difference that UIGestureRecognizer is not a subclass of UIView, but UIButton is. That's why you can just use the passed UIButton instance in your first snippet. In the second snippet you need use the view property of UIGestureRecognizer.
guard let imageView = gestureRecognizer.view as? UIImageView else { return }
Besides your actual question it seems important to clarify how to write #selectors correctly. You're doing it correct already. No change necessary. Some may say you need to add (_:) or : to your selector like so: #selector(handleSelectPhoto(_:)) but this isn't true. In general, you only need to add these special characters when you are selecting a method which has an overload method with a different amount of parameters, but the same base name.
You should make your tell while setting the selection that your function will accept a parameter by adding : at the end of method name.
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto:))
UIKit will automatically understand that the selector methods parameter will be of type UITapGestureRecognizer. Now rewrite the below method like this and you will be good to go.
#objc func handleSelectPhoto(gesture: UITapGestureRecognizer) {
if let buttonImageView = gesture.view as? UIImageView {
//Here you can make changes in imageview what ever you want.
}
}

Gesture Recognizer and UIImageView With Custom Property

My app uses images which can have various statuses so I am using custom properties as tags. This works ok, but my tap gesture recognizer can't seem to access these properties. When the image is tapped, I need the action to depend on the state of these properties. Is there a way the gesture recognizer can read these custom properties from the tapped subclassed UIImageView or should I take a different approach? Thanks!
public class advancedTagUIImageView: UIImageView {
var photoViewedStatus: Bool?
var photoLikedStatus: Bool?
}
viewDidLoad() {
let imageView = advancedTagUIImageView(frame:CGRect(origin: CGPoint(x:50, y:50), size: CGSize(width:100,height:100)))
imageView.image = UIImage(named: dog.png)
imageView.photoViewedStatus = false
imageView.photoLikeStatus = false
imageView.tag = 7
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(soundTapped)))
view.addSubview(imageView)
}
#objc func soundTapped(gesture: UIGestureRecognizer) {
let photoTag = gesture.view!.tag // this works great
let isPhotoLiked = gesture.view!.photoLikeStatus // this doesn't work
// do whatever
}
Swift is strongly typed. The type of the gesture.view property is UIView which doesn't have the properties defined in your advancedTagUIImageView class. This is because you could theoretically also attach your UITapGestureRecognizer to any other type of view. In which case the program would crash on your soundTapped method, because you're just assuming that gesture.view is an advancedTagUIImageView which might not always be the case.
For the compiler to let you access these properties you need first check if gesture.view is really your sublcass like this:
if let photoView = (gesture.view? as? advancedTagUIImageView) {
// you can access your tags here
let isPhotoLiked = photoView.photoLikeStatus
} else {
// you might want to handle the case that the gesture was invoked from another view. If you're certain this should not happen, maybe just throw an assertion error to get notified in case it still does.
}
PS: According to the Swift API Design Guidelines type names should be capitalized, so in your case it should be AdvancedTagUIImageView. Not following these guidelines might not crash your program, but doing so might make your life a lot easier should you ever need to write code together with other people.

performSegueWithIdentifier from a NSView subclass?

I have a document window that contains a number of NSView subclasses, switched between using a tab control. Each of the subclasses, and the window's ViewController, support different user actions accessed through menu items tied to the First Responder.
I'd like to perform a segue from one of those views in response to a menu item. However, NSView does not support performSegueWithIdentifier, it appears to be something that is part of NSViewController alone.
Can someone suggest a way around this? I have seen suggestions to pass the VC into the views, but I am not clear how to do that. Or perhaps there is a better way?
view.containingController.performSegue()
note: you have to add containingController to your views
I WOULD add the viewController to the responder chain and then make containingController a computed property in an extension!
e.g. add vc as responder:
override func awakeFromNib() {
super.awakeFromNib()
self.nextResponder = self.view
for subview in self.view.subviews {
subview.nextResponder = self
}
}
e.g. containingController in extension
extension NSView {
var containingController: NSViewController? {
get {
while(self.nextResponder != nil) {
if(self.nextResponder is NSViewController) {
return self.nextResponder
}
}
return nil
}
}
}
You could do that (see Daij-Djan's answer), however it is not what I would recommend, since a hypothetical programmer who will be using your code, but is not familiar with it (let's say, you in a year :) ) might be caught by surprise by such behaviour.
I would recommend you to add a delegate (conforming to your custom protocol, let's call it MyViewDelegate) to your NSView with a method like viewRequiresToPerformTransition(view: YourViewSubclass). Then you implement this method (more generally, you conform to MyViewDelegate protocol) in your view controller and inside its implementation perform any segue you want.

Table with static cells won't scroll when I set delaysContentTouches = false

The problem: HIGHLIGHT vs SCROLLING
My buttons inside the cell where not getting highlighted when I lightly tap on them. I had to tap hard and for a long time to be able to see the tap state of the button.
So I did this in order to set the delaysContentTouches to false (I didn't manage other way to do it) inside viewDidLoad():
for index in tableView.subviews {
if (index.isKindOfClass(UIScrollView)) {
let scrollViewFound = index as! UIScrollView
scrollViewFound.delegate = self
scrollViewFound.delaysContentTouches = false
scrollViewFound.canCancelContentTouches = true
scrollViewFound.scrollEnabled = true
}
}
This way the buttons highlight correctly but then I cannot scroll the table up or down, unless I start dragging from one of the empty cells --> userInteractionEnable = false in the empty cells
What I need:
To be able to highlight the buttons but also to scroll the table.
Is it even possible to have both, scrollable view and highlighted buttons?
What I have tried
I tried calling this function:
func touchesShouldCancelInContentView(view: UIView) -> Bool {
print("touchesShouldCancelInContentView happening---------")
return true
}
Which never gets called. I tried overriding But it gives an error:
Method does not override any method from its superclass
Which is weird, because UITableViewController inherits from UIScrollView. I also tried adding UIScrollViewDelegate to the class definition, but of course it gives another error that this is redundant.
Extra Information
The class is declared like this:
class Settings: UITableViewController, UITextFieldDelegate { ...
The tableView is made of Static Cells
The cells:
Some are empty: where UserInteractionEnable = false
Some have buttons with text field: I want these buttons to get highlighted. UserInteractionEnable = true. The button action is called by .TouchUpInside
Some have labels and a check image: Their action gets called in didSelectRowAtIndexPath which will change the labels colour and check images
Maybe it is relevant to say that when user clicks on any cell didSelectRowAtIndexPath it will
call a function to dismiss the keyboard.
You tried to subclass the wrong class, that's why it doesn't work. You have to subclass the UITableView class itself, and not the UITableViewController.
Can you try the following ?
- First
Subclass the TableView class in order to override the touchesShouldCancelInContentView function.
class UIDraggableTableView: UITableView {
override func touchesShouldCancelInContentView(view: UIView) -> Bool {
if (view.isKindOfClass(UIButton)) {
return true
}
return super.touchesShouldCancelInContentView(view)
}
}
- Second
In your TableViewController class, when viewDidLoad() is called, append the following right after super.viewDidLoad():
self.tableView = DraggableTableView()
This should solve your issue.
Part of this answer was taken from this StackOverflow post.

Check if subview exists in swift 1.2

I have added my subview using
self.view.addSubview(noticeSubView)
At some point I need to check if that subview exists before proceeding with some other actions etc. I found the following while searching but not sure if it is how it is done and also how to implement.
BOOL doesContain = [self.view.subviews containsObject:pageShadowView];
I appreciate any help, thanks.
Rather than asking if a subview exists in your view, you are better off asking if the subview (in your case noticeSubView) has a parent that is your view.
So in your example you would check later for:
if ( noticeSubView.superview === self.view ) {
...
}
The triple "===" is making sure that the superview object is the same object as self.view, instead of trying to call isEqual() on the view.
Another approach people have used in the past is to set an integer tag on a subview, like so:
noticeSubView.tag = 4
The default is zero so anything nonzero will be unique.
Then you can check to see if a superview contains a specific view by tag:
if ( self.view?.viewWithTag(4) != nil )
...
}
If you take that approach, you should probably create an enum for the integer value so it's clearer what you are looking for.
Note: There's a "?" after self.view because a view controller will not have a view defined until after viewDidLoad, the "?" makes sure the call will not occur if self.view returns .None
If noticeSubView is a custom class (let's call it NoticeSubView), then you can find it like this:
for view in self.view.subviews {
if let noticeSubView = view as? NoticeSubView {
// Subview exists
}
}
Or, you can assign a tag to the view and search for it.
noticeSubView.tag = 99
//...
for view in self.view.subviews {
if view.tag == 99 {
// Subview exists
}
}
best and easy way to find that your view is exist or not , there are many way like you check a view is contain or not in super view or just view some time this method is failed because if the Uiview is already remove then error occur, so code is here : here errorView is my UiView
errorView.tag = 333
if ( self.view?.viewWithTag(333) != nil ){
print("contain")
}
else {
print("not contain")
}