Resolved UIButton not responding to touch events

moonman239

macrumors 68000
Original poster
Mar 27, 2009
1,521
22
I have a button that is showing but not responding to touches. This button is created in code, inside a UIView that is also created in code. On my device screen, the button appears to be off from where it is supposed to be, however, I cannot find any reason to suspect I (I'm a team of one) am at fault for this. I was unable to successfully use the view debugger. In case this is a bug, I have already filed a report with Apple. I already reinstalled Xcode. No views are set to overlap.

Here's my code.

Code:
//
//  MenuItemView.swift
//  Reading Expressway
//
//  Created by Montana Burr on 8/10/16.
//  Copyright © 2016 Montana. All rights reserved.
//

import UIKit

enum ActivityStatus {
    case NotThereYet
    case Next
    case Completed
}
 class MenuItemView: UIView {
    var currentStatus : ActivityStatus = ActivityStatus.NotThereYet
    var starNum : Int = 1
     let button: UIButton! = UIButton()
    @IBOutlet var starImageView: UIImageView? {
        didSet {
           
            // Load star.
            let mainBundle = Bundle(for: MenuItemView.self)
            let starURL = mainBundle.path(forResource: "\(starNum)star", ofType: "png", inDirectory:"pictures")
            let starImage = UIImage(contentsOfFile: starURL!)
            starImageView!.image = starImage!
            button.bringSubview(toFront: button)
            // Hide view
            self.starImageView!.isHidden = true
            // Set star so that it doesn't stretch.
            self.starImageView!.contentMode = UIViewContentMode.scaleAspectFit
        }
    }/** Star image view. Defined in storyboard **/
    func buttonPressed() {
        (superview as! ExerciseMenuView).buttonPressed(sender: self)
    }
    init(frame: CGRect,_tag: Int)
    {
        super.init(frame: frame)
                tag = _tag;
                starImageView = UIImageView(frame: bounds)
                addSubview(starImageView!)
                addPlayButton()
    }
    func addPlayButton() // Adds the "Play" button
    {
        // Add button to self.
        self.addSubview(button)
        // Give the button its image.
        let mainBundle = Bundle(for: MenuItemView.self)
        let playURL = mainBundle.path(forResource: "play-button", ofType: "png", inDirectory:"pictures")
       
        let playImage = UIImage(contentsOfFile: playURL!)
        button.setBackgroundImage(playImage, for: UIControlState.normal)
        button.isUserInteractionEnabled = false
        // Respond to button taps
        button.addTarget(self, action: #selector(MenuItemView.buttonPressed), for: UIControlEvents.touchUpInside)
        // Make sure button doesn't accidentally resize
        button.translatesAutoresizingMaskIntoConstraints = true
        // Size button to fit in view.
        let buttonOrigin = CGPoint(x:0, y:bounds.maxY)
        let buttonSize = CGSize(width: bounds.size.width, height: 128)
        button.frame = CGRect(origin: buttonOrigin, size: buttonSize)
        // Size button image to fit
        button.contentMode = UIViewContentMode.scaleAspectFit
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
       addPlayButton()
    }
    override init(frame: CGRect)
    {
        super.init(frame: frame)
    }

    func setActivityStatus(newStatus : ActivityStatus) {
        self.isHidden = false
        currentStatus = newStatus
        switch newStatus {
        case .Next:
            button.isHidden = false
            button.isUserInteractionEnabled = true
        case .Completed:
            starImageView!.isHidden = false
            button.isUserInteractionEnabled = false
            button.isHidden = true
        default:
            button.isHidden = true
            starImageView!.isHidden = true
            break
           
        }
        // Log
        if (self.tag == 2)
        {
            print("setActivityStatus: Star image view superview is \(starImageView?.superview)")
        }
    }
    /*
    // Only override drawRect: if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func drawRect(rect: CGRect) {
        // Drawing code
    }
    */
}
 

moonman239

macrumors 68000
Original poster
Mar 27, 2009
1,521
22
OK, so maybe the problem isn't Interface Builder. I've added the view manually. Hopefully someone can tell me if I'm doing everything right. Here's my new view controller code:
Code:
//

//  ExerciseMenuViewController.m

//  Reading Expressway

//

//  Created by Montana Burr on 7/11/14.

//  Copyright (c) 2014 Montana. All rights reserved.

//


#import "ExerciseMenuViewController.h"

#import "LevelObject.h"

#import "WordBuildingExerciseViewController.h"

#import "Reading_Expressway-Swift.h"

#import "TextCell.h"

#import "OtherTableViewCell.h"

#import "PlaceEntity+CoreDataProperties.h"

#define ChildKey @"child"

#define SectionKey @"section"

#define WordFamilyObjKey @"wordObject"


@interface ExerciseMenuViewController ()


@end

@implementation ExerciseMenuViewController

{

    XMLDataObject *dataObject;

    PlaceEntity *entity;

    NSArray *menuViews;

    ExerciseMenuView *levelView; // The view corresponding to the current level.

}

@synthesize elements;

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {

       

    }

    return self;

}


-(void)viewDidLoad

{

    NSInteger level = [LevelObject currentLevel] + 1;

    // Load menu view corresponding to current level

    if (level == 1)

    {

        // Hard-code view

        levelView = [[ExerciseMenuView alloc] initWithFrame:self.view.frame];

        UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"level1homepic.png"]];

        backgroundView.frame = self.view.frame;

        levelView.backgroundImageView = backgroundView;

        [levelView addSubview:backgroundView];

        CGRect view0frame = CGRectMake(320, 491, 211, 128);

        MenuItemView *view0 = [[MenuItemView alloc] init];

        view0.frame = view0frame;

        [view0 addPlayButton];

        view0.tag = 0;

        [levelView addSubview:view0];

    }

    else {

    NSString *nibName = [NSString stringWithFormat:@"%i",level];

    NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];

    levelView = [nibViews firstObject];

    [levelView setFrame:self.view.bounds];

    [self setMenuView:levelView];

   

    }

    [self.view addSubview:levelView];

    [levelView setViewController:self];

}


- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    // Get place entity for current level

    entity = [[UserEntity sharedEntity] placeEntityForLevel:[LevelObject currentLevel]];

    elements = [self.element elementChildren];

    NSAssert([[elements.firstObject elementName] isEqualToString:@"wordFamily"], @"Expected word families.");

    // Pass relevant information to levelView

    levelView.wordFamilyIndex = entity.familyNum;

    levelView.activityIndex = entity.activityNum;

    levelView.viewController = self;

    [levelView update];

    }



-(IBAction)goBack:(id)sender {

[(AppViewController *)self.presentingViewController setElement:[self.element parentObject]];

[self.navigationController popViewControllerAnimated:false];

}


-(IBAction)activityButtonPressed:(id)sender

{

    NSMutableArray *segues = [[NSMutableArray alloc] init];

    ElementObject *wordFamily = [self.elements objectAtIndex:entity.familyNum]; // The object representing the current word family.

    ElementObject *activity; // Represents the current activity.

    if (wordFamily.elementChildren.count > 0)

    {

        activity = [wordFamily.elementChildren objectAtIndex:entity.activityNum];

    }

    else {

        activity = wordFamily;

    }

    [sender setElementObject:activity];

    if (![activity.elementName isEqualToString:@"storyRhyme"]) {

        [segues addObject:@"WordBuildingSegue"];

        ElementObject *currentWordFamily = wordFamily;

        NSString *wordFamilyName = [currentWordFamily.attributeDictionary valueForKey:@"name"];

        if ([entity level] > 0 || [wordFamilyName isEqualToString:@"at"]) {

            [segues addObjectsFromArray:@[@"WordBuildingSegue",@"SentenceBuildingSegue"]];

            }

        else

        {

            // ap

           

            [segues addObject:@"SentenceBuildingSegue"];

        }

        }

        else

        {

            [segues addObject:@"StoryRhymeSegue"];

        }

    NSString *segueIdentifier = [segues objectAtIndex:entity.activityNum];

    [self performSegueWithIdentifier:segueIdentifier sender:sender];

}


- (void)didReceiveMemoryWarning

{

    [super didReceiveMemoryWarning];

    NSLog(@"Exercise menu view received memory warning");

    // Dispose of any resources that can be recreated.

}


@end
And here's the code for the view that contains the view that in turn contains the button:
Code:
//

//  MenuItemView.swift

//  Reading Expressway

//

//  Created by Montana Burr on 8/10/16.

//  Copyright © 2016 Montana. All rights reserved.

//


import UIKit


enum ActivityStatus {

    case NotThereYet

    case Next

    case Completed

}

 class MenuItemView: UIView {

    var currentStatus : ActivityStatus = ActivityStatus.NotThereYet

    var starNum : Int = 1

    let button: UIButton! = UIButton()

    @IBOutlet var starImageView: UIImageView! {

        didSet {

           

            // Load star.

            let mainBundle = Bundle(for: MenuItemView.self)

            let starURL = mainBundle.path(forResource: "\(starNum)star", ofType: "png", inDirectory:"pictures")

            let starImage = UIImage(contentsOfFile: starURL!)

            starImageView!.image = starImage!

            button.bringSubview(toFront: button)

            // Hide view

            self.starImageView!.isHidden = true

            // Set star so that it doesn't stretch.

            self.starImageView!.contentMode = UIViewContentMode.scaleAspectFit

        }

    }/** Star image view. Defined in storyboard **/

    func buttonPressed() {

        (superview as! ExerciseMenuView).buttonPressed(sender: self)

    }

    init(frame: CGRect,_tag: Int)

    {

        super.init(frame: frame)

                tag = _tag;

                starImageView = UIImageView(frame: bounds)

                addSubview(starImageView!)

                addPlayButton()

    }

    func addPlayButton() // Adds the "Play" button

    {

        // Add button to self.

        self.addSubview(button)

        // Give the button its image.

        let mainBundle = Bundle(for: MenuItemView.self)

        let playURL = mainBundle.path(forResource: "play-button", ofType: "png", inDirectory:"pictures")

       

        let playImage = UIImage(contentsOfFile: playURL!)

        button.setBackgroundImage(playImage, for: UIControlState.normal)

        button.isUserInteractionEnabled = false

        // Respond to button taps

        button.addTarget(self, action: #selector(MenuItemView.buttonPressed), for: UIControlEvents.touchUpInside)

        // Make sure button doesn't accidentally resize

        button.translatesAutoresizingMaskIntoConstraints = true

        // Size button to fit in view.

        let buttonOrigin = CGPoint(x:0, y:bounds.maxY)

        let buttonSize = CGSize(width: bounds.size.width, height: 128)

        button.frame = CGRect(origin: buttonOrigin, size: buttonSize)

        // Size button image to fit

        button.contentMode = UIViewContentMode.scaleAspectFit

    }

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)

      addPlayButton()

    }

    override init(frame: CGRect)

    {

        super.init(frame: frame)

    }


    func setActivityStatus(newStatus : ActivityStatus) {

        self.isHidden = false

        currentStatus = newStatus

        switch newStatus {

        case .Next:

            button.isHidden = false

            button.isUserInteractionEnabled = true

        case .Completed:

            starImageView!.isHidden = false

            button.isUserInteractionEnabled = false

            button.isHidden = true

        default:

            button.isHidden = true

            starImageView!.isHidden = true

            break

           

        }

        // Log

        if (self.tag == 2)

        {

            print("setActivityStatus: Star image view superview is \(String(describing: starImageView?.superview))")

        }

    }

    /*

    // Only override drawRect: if you perform custom drawing.

    // An empty implementation adversely affects performance during animation.

    override func drawRect(rect: CGRect) {

        // Drawing code

    }

    */

}
 

moonman239

macrumors 68000
Original poster
Mar 27, 2009
1,521
22
Again you keep doing things the hard way.

But here's a Swift 3 answer to a similar question:

http://stackoverflow.com/questions/43448101/ios-swift-3-custom-uibutton-using-xib
I tried using an XIB. It worked for a while, and then something happened unexpectedly. What you see here is an attempt to work around the previous issue.
Anyways, I've been meaning to try setting the MenuItemView's userInteractionEnabled property to true. I'll see if that fixes it, and I'll post the results back ASAP.
[doublepost=1492729720][/doublepost]And...that did nothing.
 

PhoneyDeveloper

macrumors 68040
Sep 2, 2008
3,114
93
You're supposed to use UIButton(type: theType) to create buttons, not UIButton(). You can set the frame or constraints later.
 

moonman239

macrumors 68000
Original poster
Mar 27, 2009
1,521
22
You're supposed to use UIButton(type: theType) to create buttons, not UIButton(). You can set the frame or constraints later.
Thanks for the tip. I have another thread going on in Apple's developer forum . Someone on that forum mentioned that as well.

Here's the link to the other thread: https://forums.developer.apple.com/message/224313#224313

What's worse - if I go back to a previous build that uses XIBs, I see nothing - which is strange.
 

bjet767

macrumors 6502a
Oct 2, 2010
961
307
So why aren't you using the Storyboard and then just link a button to what you visually add to your view?

The only reason to build it from code is because you want it to not be part of the view when it appears.

You do know any view can be made invisible and then appear when it is needed?

The first rule of iOS programming is, when possible, to only code what is needed for functionality and appearances. Leave the layouts and segue building to the Storyboard. Even then most apps will grow to many lines of code.
 

moonman239

macrumors 68000
Original poster
Mar 27, 2009
1,521
22
OK, so as I said before, I went back to using an XIB. Tried to fix my problem by adding the button in the XIB file (and subsequently commenting out code that adds the button), and I made sure to have the button show up inside the superview's frame in IB.
Now, I changed the button's superview background color to "white" so I could see that the view was appearing on the screen.

Edit: I realize now that I've missed a line that also triggers creation of a button. I've commented out and am running the app now, though, to see what happens.

Edit #2: Also, sorry for all the threads I've created. My app seems to be so inexplicably erroneous that some of my codebase has increased in complexity.
 
Last edited:

bjet767

macrumors 6502a
Oct 2, 2010
961
307
Moonman

Are you relatively new to iOS programming? I'm asking you from a serious position and not to put you down.

The reason I ask is because you seem to be doing things contrary to normal iOS app programming methodology.
Apple has a huge document base with examples that covers much of what you keep posting about and stack overflow is a great place to search out solutions where others have gone before you.

I have an app in beta which uses Bluetooth, location services, WatchKit, HealthKit, animation, iCloud for file storage, UIDocument for record saving, printing, user defaults, tab navigation, hidden views and a bit more. In it all I have never used or found the need to use an XIB, just plain old story boarding with uiviewcontrollers gets the job done. Yes I have added sub-views through coding and sub-layers for animation speed and have a number of NSObject based objects, but at the heart of it all is a Storyboard.

What I'm encouraging you to do is embrace the Storyboard for laying out you app, from entry to the most complicated views. It's the Apple preferred method.

Enjoy the adventure!
 

moonman239

macrumors 68000
Original poster
Mar 27, 2009
1,521
22
Moonman

Are you relatively new to iOS programming? I'm asking you from a serious position and not to put you down.

The reason I ask is because you seem to be doing things contrary to normal iOS app programming methodology.
Apple has a huge document base with examples that covers much of what you keep posting about and stack overflow is a great place to search out solutions where others have gone before you.

I have an app in beta which uses Bluetooth, location services, WatchKit, HealthKit, animation, iCloud for file storage, UIDocument for record saving, printing, user defaults, tab navigation, hidden views and a bit more. In it all I have never used or found the need to use an XIB, just plain old story boarding with uiviewcontrollers gets the job done. Yes I have added sub-views through coding and sub-layers for animation speed and have a number of NSObject based objects, but at the heart of it all is a Storyboard.

What I'm encouraging you to do is embrace the Storyboard for laying out you app, from entry to the most complicated views. It's the Apple preferred method.

Enjoy the adventure!
I actually fixed the issue. I noticed I was missing a call to [super viewDidLoad].
In any case, I stick as close to XIBs as I can. In fact, most/all of my interface is built using a combination of XIBs and storyboards. It's when I don't know why something doesn't work when I tend to take a different path.
 

bjet767

macrumors 6502a
Oct 2, 2010
961
307
I stick as close to XIBs as I can. In fact, most/all of my interface is built using a combination of XIBs and storyboards.
OK, just seems a bit harder (more code) and, in my opinion, doesn't add any real flexibility or speed to the mix.

The idea of UIViewControllers as the main workhorse is they do a whole lot of for you and that means less coding/typing.

In OSX I was happy when they added storyboarding like iOS to the xCode interface. What it did in particular was separated out code which really should not have been included in certain files. My best example would be working with NSDocuments.

Before OSX storyboarding the NSDocument included a whole bunch of interface stuff that really should have been in a view controller. With view controllers I now have almost a transferrable code between NSDocuments and UIDocuments so file compatibility is almost 100% (some of the data types, like images, are on fully compatible) and concentrates on the document's I/O while leave all the interface stuff to the View Controller.

One possible down side to view controllers is it can be forgotten they include a view that is initialized with the controller. Making custom changes to the view are often not as obvious as when the view is initialized separately.


However, to each his own and press on with your project!