How to use a UIButton as a toggle switch - iphone

I am using a UIButton of custom type and what I want is use it like a toggle switch with the change of image. Like when it is clicked if previously it was not in selected mode it should go in selected mode or otherwise vice-a-versa. Also it will have a different image and when it is selected it will have a different image when it is not.
I am not able to do it programatically, is there any good easy way to do this.

I believe this post has a better solution : iPhone UIButton with UISwitch functionality
UIButton has toggling built in. There is a selected property that you can set and change the skins based on the state.

In your header file add:
IBOutlet UIButton *toggleButton;
BOOL toggleIsOn;
#property (nonatomic, retain) IBOutlet UIButton *toggleButton;
In the implementation:
- (IBACtion)toggle:(id)sender
{
if(toggleIsOn){
//do anything else you want to do.
}
else {
//do anything you want to do.
}
toggleIsOn = !toggleIsOn;
[self.toggleButton setImage:[UIImage imageNamed:toggleIsOn ? #"on.png" :#"off.png"] forState:UIControlStateNormal];
}
then link your button with the IBActions and the IBOutlet and initialize toggleIsOn to NO.

In the interface:
#interface TransportViewController : UIViewController {
UIButton *button;
}
#property(nonatomic, retain) UIButton *button;
In the implementation:
- (void)loadView {
[super loadView];
...
[self setButton:[UIButton buttonWithType:UIButtonTypeCustom]];
[button addTarget:self action:#selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
[button setImage:[UIImage imageNamed:#"image1"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:#"image2"] forState:UIControlStateSelected];
}
- (void) onClick:(UIButton *)sender {
[sender setSelected:!sender.selected];
}

UIButton does supports a "toggle" functionality by default. To use this you need to set a different image or even text color in Interface Builder for the State Configuration = Selected, and use the selected property of UIButton to toggle its state.
Code:
- (IBAction)yourButtonTouch:(UIButton *)sender {
sender.selected = !sender.selected;
if (sender.selected) {
//...
// Action to be performed when button is selected or
// the first touch
// ...
}
}

For Swift 3
#IBAction func playPause(sender : UIButton){
if sender.isSelected {
player.pause()
}else{
player.play()
}
sender.isSelected = !sender.isSelected
}

- (IBAction)buttonTapped:(UIButton *)sender {
//first time sender.selected is No
if (sender.selected) {
//code here
sender.selected=NO;
}
else{
//code here
sender.selected=YES;
}
}

The only problem here is that you have to use 2 images to achieve toggling. Also you can't use highlighted property because of UIButton (UIControl) automatically sets this property under the hood, to be exact in touchBegan:, touchMoved:, etc. methods. The best way I offer is to simple use subclassing:
#interface ToggleButton : UIButton
#end
#implementation ToggleButton
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
self.highlighted = self.selected = !self.selected;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved:touches withEvent:event];
self.highlighted = self.selected;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesEnded:touches withEvent:event];
self.highlighted = self.selected;
}
- (void)setSelected:(BOOL)selected{
[super setSelected:selected];
self.highlighted = selected;
}
#end
This is quite enough to make your toggle working

In order to do so we can use UIButton Subclass:
class UIToggleButton: UIButton {
fileprivate let onImage: UIImage
fileprivate let offImage: UIImage
fileprivate let target: AnyObject
fileprivate let onAction: Selector
fileprivate let offAction: Selector
var isOn: Bool {
didSet {
let image = isOn ? onImage : offImage
setImage(image, for: UIControlState())
}
}
init(onImage: UIImage,
offImage: UIImage,
target: AnyObject,
onAction: Selector,
offAction: Selector)
{
isOn = false
self.onImage = onImage
self.offImage = offImage
self.target = target
self.onAction = onAction
self.offAction = offAction
super.init(frame: CGRect.zero)
setImage(offImage, for: UIControlState())
addTarget(self, action: #selector(UIToggleButton.tapAction), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func tapAction() {
let sel = isOn ? onAction : offAction
isOn = !isOn
_ = target.perform(sel)
}
}

My implementation of a UIButton as a Switch.
class ButtonSwitch: UIButton {
override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
if allControlEvents == .touchUpInside {
isSelected.toggle()
}
super.sendAction(action, to: target, for: event)
}
}

Change button image isn't a difficult task. And you can use some BOOL value to detect if your switch-button is in "ON" state. and in your onBtnClk method you can just change state of your BOOL value and set image for current state.

I made this class, changing the class to PGToggleButton in the Interface builder will do it. It uses the images for Default and Highlighted state, and has a public property to get/set the actual state.
PGToggleButton.h
#interface PGToggleButton : UIButton
#property (nonatomic, getter=isOn) BOOL on;
-(void)toggle;
#end
PGToggleButton.m
#import "PGToggleButton.h"
#interface PGToggleButton ()
#property (nonatomic, strong) UIImage *offStateImage;
#property (nonatomic, strong) UIImage *onStateImage;
-(void)touchedUpInside:(UIButton*) sender;
#end
#implementation PGToggleButton
#synthesize on = _on;
#synthesize offStateImage = _offStateImage;
#synthesize onStateImage = _onStateImage;
-(void)awakeFromNib
{
[super awakeFromNib];
self.offStateImage = [self imageForState:UIControlStateNormal];
self.onStateImage = [self imageForState:UIControlStateHighlighted];
[self addTarget:self
action:#selector(touchedUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
-(void)touchedUpInside:(UIButton*) sender
{ [self toggle]; }
-(void)toggle
{ self.on = toggle(_on); }
-(void)setOn:(BOOL) on
{
_on = on;
if (on)
[self setImage:self.onStateImage forState:(UIControlStateNormal)];
else
[self setImage:self.offStateImage forState:(UIControlStateNormal)];
}
#end

Related

UIButton not showing highlight on tap in iOS7

I've looked at a ton of posts on similar things, but none of them quite match or fix this issue. Since iOS 7, whenever I add a UIButton to a UITableViewCell or even to the footerview it works "fine", meaning it receives the target action, but it doesn't show the little highlight that normally happens as you tap a UIButton. It makes the UI look funky not showing the button react to touch.
I'm pretty sure this counts as a bug in iOS7, but has anyone found a solution or could help me find one :)
Edit:
I forgot to mention that it will highlight if I long hold on the button, but not a quick tap like it does if just added to a standard view.
Code:
Creating the button:
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.titleLabel.font = [UIFont systemFontOfSize:14];
button.titleLabel.textColor = [UIColor blueColor];
[button setTitle:#"Testing" forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents: UIControlEventTouchDown];
button.frame = CGRectMake(0, 0, self.view.frame.size.width/2, 40);
Things I've Tested:
//Removing gesture recognizers on UITableView in case they were getting in the way.
for (UIGestureRecognizer *recognizer in self.tableView.gestureRecognizers) {
recognizer.enabled = NO;
}
//Removing gestures from the Cell
for (UIGestureRecognizer *recognizer in self.contentView.gestureRecognizers) {
recognizer.enabled = NO;
}
//This shows the little light touch, but this isn't the desired look
button.showsTouchWhenHighlighted = YES;
In that tableview you just add this property.
tableview.delaysContentTouches = NO;
And add in cellForRowAtIndexPath after you initiate the cell you just add below code. The structure of the cell is apparently different in iOS 6 and iOS 7.
iOS 7 we have one control UITableViewCellScrollView In between UITableViewCell and content View.
for (id obj in cell.subviews)
{
if ([NSStringFromClass([obj class]) isEqualToString:#"UITableViewCellScrollView"])
{
UIScrollView *scroll = (UIScrollView *) obj;
scroll.delaysContentTouches = NO;
break;
}
}
Since iOS 8 we need to apply the same technique to UITableView subviews (table contains a hidden UITableViewWrapperView scroll view). There is no need iterate UITableViewCell subviews anymore.
for (UIView *currentView in tableView.subviews) {
if ([currentView isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)currentView).delaysContentTouches = NO;
break;
}
}
This answer should be linked with this question.
I tried to add this to the accepted answer but it never went through. This is a much safer way of turning off the cells delaysContentTouches property as it does not look for a specific class, but rather anything that responds to the selector.
In Cell:
for (id obj in self.subviews) {
if ([obj respondsToSelector:#selector(setDelaysContentTouches:)]) {
[obj setDelaysContentTouches:NO];
}
}
In TableView:
self.tableView.delaysContentTouches = NO;
For a solution that works in both iOS7 and iOS8, create a custom UITableView subclass and custom UITableViewCell subclass.
Use this sample UITableView's initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// iterate over all the UITableView's subviews
for (id view in self.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
Use this sample UITableViewCell's initWithStyle:reuseIdentifier:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
// iterate over all the UITableViewCell's subviews
for (id view in self.subviews)
{
// looking for a UITableViewCellScrollView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewCellScrollView"])
{
// this test is here for safety only, also there is no UITableViewCellScrollView in iOS8
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
What I did to solve the problem was a category of UIButton using the following code :
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = YES; }];
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self performSelector:#selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self performSelector:#selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)setDefault
{
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = NO; }];
}
the button reacts correctly when I press on it in a UITableViewCell, and my UITableView behaves normally as the delaysContentTouches isn't forced.
Here's Roman B's answer in Swift 2:
for view in tableView.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
break
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (id view in self.tableView.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
I was having similar issues with a text-only UIButton in a UITableViewCell not highlighting upon touch. What fixed it for me was changing the buttonType from Custom back to System.
Setting delaysContentTouches to NO did the trick for the image-only UIButton in the same UITableViewCell.
self.tableView.delaysContentTouches = NO;
This is a Swift version of Raphaƫl Pinto's answer above. Don't forget to upvote him too :)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = true }
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
func setDefault() {
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = false }
}
Solution in Swift, iOS8 only (needs the extra work on each of the cells for iOS7):
//
// NoDelayTableView.swift
// DivineBiblePhone
//
// Created by Chris Hulbert on 30/03/2015.
// Copyright (c) 2015 Chris Hulbert. All rights reserved.
//
// This solves the delayed-tap issue on buttons on cells.
import UIKit
class NoDelayTableView: UITableView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delaysContentTouches = false
// This solves the iOS8 delayed-tap issue.
// http://stackoverflow.com/questions/19256996/uibutton-not-showing-highlight-on-tap-in-ios7
for view in subviews {
if let scroll = view as? UIScrollView {
scroll.delaysContentTouches = false
}
}
}
override func touchesShouldCancelInContentView(view: UIView!) -> Bool {
// So that if you tap and drag, it cancels the tap.
return true
}
}
To use, all you have to do is change the class to NoDelayTableView in your storyboard.
I can confirm that in iOS8, buttons placed inside a contentView in a cell now highlight instantly.
Slightly modified version of Chris Harrison's answer. Swift 2.3:
class HighlightButton: UIButton {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = true }
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
setDefault()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
setDefault()
}
private func setDefault() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = false }
}
}
}
The accepted answer did not work at some "taps" for me .
Finally I add the bellow code in a uibutton category(/subclass),and it works a hundred percent.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.backgroundColor = [UIColor greenColor];
[UIView animateWithDuration:0.05 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.backgroundColor = [UIColor clearColor];
} completion:^(BOOL finished)
{
}];
[super touchesBegan:touches withEvent:event];
}
I wrote a category extension on UITableViewCell to make this issue simple to address. It does basically the same thing as the accepted answer except I walk up the view hierarchy (as opposed to down) from the UITableViewCell contentView.
I considered a fully "automagic" solution that would make all cells added to a UITableView set their delaysContentTouches state to match the owning UITableView's delaysContentTouches state. To make this work I'd have to either swizzle UITableView, or require the developer to use a UITableView subclass. Not wanting to require either I settled on this solution which I feel is simpler and more flexible.
Category extension and sample harness here:
https://github.com/TomSwift/UITableViewCell-TS_delaysContentTouches
It's dead-simple to use:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// using static cells from storyboard...
UITableViewCell* cell = [super tableView: tableView cellForRowAtIndexPath: indexPath];
cell.ts_delaysContentTouches = NO;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Here's the code for the category:
#interface UITableViewCell (TS_delaysContentTouches)
#property (nonatomic, assign) BOOL ts_delaysContentTouches;
#end
#implementation UITableViewCell (TS_delaysContentTouches)
- (UIScrollView*) ts_scrollView
{
id sv = self.contentView.superview;
while ( ![sv isKindOfClass: [UIScrollView class]] && sv != self )
{
sv = [sv superview];
}
return sv == self ? nil : sv;
}
- (void) setTs_delaysContentTouches:(BOOL)delaysContentTouches
{
[self willChangeValueForKey: #"ts_delaysContentTouches"];
[[self ts_scrollView] setDelaysContentTouches: delaysContentTouches];
[self didChangeValueForKey: #"ts_delaysContentTouches"];
}
- (BOOL) ts_delaysContentTouches
{
return [[self ts_scrollView] delaysContentTouches];
}
#end
Since objc is dynamic, and scrollView is the only class that responds to delaysContentTouches, this should work for both ios 7 and 8 (put it somewhere early in your tableViewController, like awakeFromNib):
for (id view in self.tableView.subviews)
{
if ([view respondsToSelector:#selector(delaysContentTouches)]) {
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.delaysContentTouches = NO;
break;
}
}
You may also have to turn off "delaysContentTouches" in your storyboard or nib by selecting the table inside your viewController. BTW, this might not work on ios 7 if you're using a tableView inside a viewController, at least I couldn't get it to work.
That solution for me doesn't work, I fixed subclassing TableView and implementing these two methods
- (instancetype)initWithCoder:(NSCoder *)coder{
self = [super initWithCoder:coder];
if (self) {
for (id obj in self.subviews) {
if ([obj respondsToSelector:#selector(setDelaysContentTouches:)]){
[obj performSelector:#selector(setDelaysContentTouches:) withObject:NO];
}
}
}
return self;
}
- (BOOL)delaysContentTouches{
return NO;
}
Solution in Swift for iOS 7 and 8:
First I wrote a utility function:
class func classNameAsString(obj: AnyObject) -> String {
return _stdlib_getDemangledTypeName(obj).componentsSeparatedByString(".").last!
}
then I subclass UITableView and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewWrapperView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
break
}
}
}
I also subclass UITableViewCell and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewCellScrollView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
}
}
}
In my case the init(coder:) will run. Please put debug point in your init functions to know which init function will run, then using the code above to make it work.
Hope to help someone.
In Swift 3 this UIView extension can be used on the UITableViewCell. Preferably in the cellForRowAt method.
func removeTouchDelayForSubviews() {
for subview in subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delaysContentTouches = false
} else {
subview.removeTouchDelayForSubviews()
}
}
}

Dismissing UIPickerView on tapping background

I am adding a uipickerview as the subview of the main view. To dismiss the pickerview on tapping the backgroud view, i am adding a UITapGestureRecognizer to the main view.
I am using the following code to add the GestureRecognizer for main view
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
gestureRecognizer.numberOfTapsRequired=1;
gestureRecognizer.numberOfTouchesRequired=1;
gestureRecognizer.delegate = self;
[self.view addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
In the handleSingleTap method i am dismissing the pickerview.
But the problem is handleSingleTap is also called when I tap inside the pickerview. To avoid it i used the following delegate method of UIGestureRecognizer
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
/*
*If the tap is inside a button return NO, to ensure the button click is detected.
*/
if ([touch.view isKindOfClass:[UIButton class]]){
return FALSE;
}else if([touch.view isKindOfClass:[UIPickerView class]]) {
return FALSE;
}
return TRUE;
}
It is working for button,But is not working for UIPickerView. Can anyone help me with this?
I have coded up a solution to your particular requirement.
first, i implemented your code as you have described and observed the same problem you reported - spurious tap events being sent to tap handler, when you tapped on anything, including a UIButton.
this told me that the UITapGestureRecogniser was "stealing" the touches that should have gone to the UIButton, so i decided the simplest, most pragmatic solution was to use that feature to my advantage, and so i assigned a UITapGestureRecogniser to both the pickerview and the button also. the taps for the pickerview we just discard, the others we parse and pass on to the button's tap handler.
note - for expedience i assigned the pickerview's datasource and delegate in the xib. you will need to do that also, or set it in code.
header
//
// ViewController.h
// stackExchangeDemo
//
// Created by unsynchronized on 18/01/12.
// released to public domain via http://stackoverflow.com/a/8908028/830899
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource>
{
UIButton *btn1;
UIPickerView *picker1;
}
#property (retain, nonatomic) IBOutlet UIButton *btn1;
#property (retain, nonatomic) IBOutlet UIPickerView *picker1;
#end
implementation
//
// ViewController.m
// stackExchangeDemo
//
// Created by unsynchronized on 18/01/12.
// released to public domain via http://stackoverflow.com/a/8908028/830899
//
#import "ViewController.h"
#implementation ViewController
#synthesize btn1;
#synthesize picker1;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
-(void) handleSingleTap:(UITapGestureRecognizer *) tapper {
if (tapper.state == UIGestureRecognizerStateEnded) {
NSLog(#"%#",NSStringFromSelector(_cmd));
}
}
- (IBAction)handleButtonTap:(id)sender {
NSLog(#"%#",NSStringFromSelector(_cmd));
}
-(void) handleButtonTapGesture:(UITapGestureRecognizer *) tapper {
// call the buttons event handler
UIControlEvents eventsToHandle = UIControlEventTouchUpInside;
if (tapper.state == UIGestureRecognizerStateEnded) {
UIButton *btn = (UIButton *) tapper.view;
for (NSString *selName in [btn actionsForTarget:self forControlEvent:eventsToHandle]) {
SEL action = NSSelectorFromString(selName);
if (action) {
[self performSelector:action withObject:btn1];
break;
}
};
}
}
-(void) handleDummyTap:(UITapGestureRecognizer *) tapper {
// silently ignore the tap event for this view.
}
-(void) setupTap:(UIView *) view action:(SEL)action {
// assign custom tap event handler for given view.
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:action];
[view addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setupTap:self.view action:#selector(handleSingleTap:)];
[self setupTap:picker1 action:#selector(handleDummyTap:)];
[self setupTap:btn1 action:#selector(handleButtonTapGesture:)];
}
- (void)viewDidUnload
{
[self setBtn1:nil];
[self setPicker1:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark #protocol UIPickerViewDataSource<NSObject>
// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return 1;
}
#pragma mark #protocol UIPickerViewDelegate<NSObject>
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return [#"so long and thanks for all the fish".copy autorelease ];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
NSLog(#"%#",NSStringFromSelector(_cmd));
}
- (void)dealloc {
[btn1 release];
[picker1 release];
[super dealloc];
}
#end
It is possible that the view touched (touch.view) is one of the subviews of the pickerview. I'd try testing:
[[pickerview subviews] containsObject: touch.view];
I implemented the following in Swift:
override func viewDidLoad()
{
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
func handleTapGesture(sender: AnyObject)
{
let subview = view?.hitTest(sender.locationInView(view), withEvent: nil)
if(!(subview?.isDescendantOfView(timePicker) ?? false))
{//might want to add a condition to make sure it's not your button ^^
showTimePicker(false)//method which handles showing/hiding my picker
}
}
I simply added an invisible UIControl instance behind the UIPickerView, which covers all the window, and gets all the touches behind UIPickerView. If it is touched, then both the UIPickerView and the UIControl is dismissed. (SelectButton and CancelButton are accessory buttons to UIPickerView.)
#property (strong, nonatomic) UIControl *touchRecognizer;
- (IBAction)showPicker:(id)sender {
self.touchRecognizer = [[UIControl alloc]initWithFrame:self.view.window.bounds];
[self.view.window addSubview:self.touchRecognizer];
[self.touchRecognizer addTarget:self action:#selector(touchedOutsidePicker:) forControlEvents:UIControlEventTouchUpInside];
[self.textField becomeFirstResponder];
}
- (IBAction)touchedOutsidePicker:(id)sender {
[self.touchRecognizer removeFromSuperview];
self.touchRecognizer = nil;
[self.textField resignFirstResponder];
}
-(void)selectButtonPressed:(id)sender{
[self.touchRecognizer removeFromSuperview];
self.touchRecognizer = nil;
[self.textField resignFirstResponder];
}
-(void)cancelButtonPressed:(id)sender{
[self.touchRecognizer removeFromSuperview];
self.touchRecognizer = nil;
[self.textField resignFirstResponder];
}

Objective-C Toggle Between Two Buttons

I'm new to Objective-C, and I'm trying to create an app with two buttons that toggle off and on in tandem. I've handled the button states (images for on and off) in Interace Bulder, but I'm having trouble figuring out exactly how to write the logic in Xcode.
Here are the conditions I need to fulfill:
-When neither button is on, either button may be turned on.
-When button1 is on, and button2 is clicked, button1 turns off, and button 2 turns on.
-When button2 is on, and button1 is clicked, button2 turns off, and button 1 turns on.
-When button1 is on and button1 is clicked, nothing happens.
-When button2 is on and button2 is clicked, nothing happens.
I've been using BOOL to try and work out the logic, but it's just not happening for me. Does anyone have an idea of how to do this?
The buttons were added programatiaclly, so the simple code looks like this in the .h file:
in .h:
#import <UIKit/UIKit.h>
#interface Profile_Settings_PageViewController : UIViewController {
IBOutlet UIButton *Button1;
IBOutlet UIButton *Button2;
BOOL ButtonSelected;
}
#property (nonatomic, retain) UIButton *Button1;
#property (nonatomic, retain) UIButton *Buton2;
-(IBAction) ButtonTouched:(id)sender;
#end
then the .m file:
#import "Profile_Settings_PageViewController.h"
#implementation Profile_Settings_PageViewController
#synthesize Button1;
#synthesize Button2;
-(IBAction) ButtonTouched:(id)sender
{
if (ButtonSelected == 0)
{
[Button1 setSelected: NO];
[Button2 setSelected: NO];
ButtonSelected = 1;
}
else if (ButtonSelected == 1)
{
[Button1 setSelected: YES];
[Button2 setSelected: YES];
ButtonSelected = 0;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
ButtonSelected == 0;
}
- (void)dealloc {
[Button1 release];
[Button2 release];
[super dealloc];
}
#end
The issue I'm having is where the if statement begins. I'm not sure how to point to the specific buttons for the logic. I know what's there now is not correct both because it applies to both buttons, and because the logic is wrong, which creates problems when writing the conditional statements. I'm not sure how to fix it though...
If your image states are for NORMAL and SELECTED, create two buttons with outlets, tag them 1 & 2 respectively, and in your button action method:
Start with button 1 selected, button 2 normal.
- (IBAction)buttonAction:(UIButton*)sender
{
if (sender.selected)
return;
if (sender.tag == 1)
{
button1.selected = !button1.selected;
button2.selected = !button1.selected;
}
else if (sender.tag == 2)
{
button2.selected = !button2.selected;
button1.selected = !button2.selected;
}
}
int selectedIndex = -1;
if( selectedIndex != 1 && b1 clicked ) { selectedIndex = 1; do stuff }
if( selectedIndex != 2 && b2 clicked ) { selectedIndex = 2; do stuff }
i.e.:
have a "which button is selected"
state
have a "nothing is selected"
state
check each click against that
selected/not-selected, and update it
when things happen
Assuming you have figured out how to wire up your buttons in IB so that they call a method in your viewController when switched. Define two outlets in your view controller (one for each button) and an action to be called when the value of a switch changes:
#interface ToggleViewController : UIViewController {
UISwitch *button1;
UISwitch *button2;
}
#property (nonatomic, retain) IBOutlet UISwitch *button1;
#property (nonatomic, retain) IBOutlet UISwitch *button2;
- (IBAction)switchAction:(id)sender;
#end
Ensure you connect the outlets and also connect the Value Changed event to the switchAction for both buttons. The switch action method can then be something along these lines:
- (IBAction)switchAction:(id)sender {
if ([sender isOn]) {
if ([sender isEqual:button1])
{
[button2 setOn:NO animated:YES];
} else {
[button1 setOn:NO animated:YES];
}
}
}
It's pretty simple: when either button is touched, you want to turn the other off. So in the UIControlEventTouchUpInside handler for button1, set button2 off. And in the UIControlEventTouchUpInside handler for button2, set button1 off.
For swift3 this is working
#IBAction func mealsButtonAction(_ sender: UIButton)
{
if sender .isEqual(beforeMealsButton)
{
beforeMealsButton.isSelected = true
beforeMealsButton .setImage(UIImage.init(named: "before_lunch_hover"), for: .normal)
afterMealsButton .setImage(UIImage.init(named: "after_lunch_normal"), for: .normal)
boolDict["bm"] = true
boolDict["am"] = false
}
else
{
afterMealsButton.isSelected = true
beforeMealsButton .setImage(UIImage.init(named: "before_lunch_normal"), for: .normal)
afterMealsButton .setImage(UIImage.init(named: "after_lunch_hover"), for: .normal)
boolDict["bm"] = false
boolDict["am"] = true
}
}
Thank you #Rog.

Detect if UIDatePicker is rolling?

I'm using a UIDatePicker in a modal view, to select a date. I found that if the view is closed while the date picker is still rolling, I got a EXC_BAD_ACCESS.
How can I detect of the date picker is rolling when the user tab a button?
I had two ideas:
1) detect if the value has changed with this:
[myDatePicker addTarget:self action:#selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
But I would need to be able to detect if the value WILL change.
2) check if the date is valid, but when the date picker is rolling, the previous date is returned, and of course is valid.
Any ideas?
In order to avoid this crash, you don't need to try to predict when the picker will try to send an action method call to your class, instead, you simply remove the picker's event action in your ViewController's dealloc routine.
EDIT: oops, as was pointed out in a comment, the UIDatePicker doesn't have a delegate. But you can assign it action events. So instead, any actions set pointing to the object that is being dealloc'd should be removed.
- (void dealloc
{
[myPicker removeTarget:self action:... forControlEvents:...];
[myPicker release];
[super dealloc];
}
I know that I am quite late, but I have just created a custom UIDatePicker subclass with a delegate:
This is the delegate:
// In TDDatePickerDelegate.h
#import <Foundation/Foundation.h>
#import "TDDatePicker.h"
#class TDDatePicker;
#protocol TDDatePickerDelegate
#required
- (void)datePickerChanged:(TDDatePicker *)datePicker newDate:(NSDate *)newDate;
#end
And this is the subclass
// In TDDatePicker.h
#import <UIKit/UIKit.h>
#import "TDDatePickerDelegate.h"
#interface TDDatePicker : UIDatePicker
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated;
- (void)dateChanged;
#property (nonatomic, assign) IBOutlet id <TDDatePickerDelegate> delegate;
#end
// In TDDatePicker.m
#import "TDDatePicker.h"
#implementation TDDatePicker
#synthesize delegate;
- (id)init {
self = [super init];
if (self) {
[self addTarget:self action:#selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self addTarget:self action:#selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self addTarget:self action:#selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}
return self;
}
- (void)dateChanged {
[delegate datePickerChanged:self newDate:self.date];
}
- (void)dealloc {
[self removeTarget:self action:#selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}
#end
Objective-C:
[datePicker addTarget:self action:#selector(isRolled) forControlEvents:UIControlEventValueChanged];
- (void)isRolled {
NSLog(#"Date Picker has been rolled.");
}
Swift:
datePicker.addTarget(self, action: "isRolled", forControlEvents: .ValueChanged)
func isRolled() {
print("Date Picker has been rolled.")
}
Officially, you can't retrieve the state of the picker in these terms with just Apple's official UIDatePicker methods.
If there are solutions out there, however, I'd be curious to see them.
There is a trick to detect this but there is no delegate method/ property to detect if its scrolling or not
take a property as isScrolling
set isScrolling to true in func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? or equivalent method
set isScrolling to true in func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
enable the close button for modal view only if picker has reached to a finite state that is scrolling is disabled
hope this helps

How can I loop through all subviews of a UIView, and their subviews and their subviews

How can I loop through all subviews of a UIView, and their subviews and their subviews?
Use recursion:
// UIView+HierarchyLogging.h
#interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy;
#end
// UIView+HierarchyLogging.m
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy
{
NSLog(#"%#", self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy];
}
}
#end
// In your implementation
[myView logViewHierarchy];
Well here is my solution using recursion and a wrapper(category/extension) for the UIView class.
// UIView+viewRecursion.h
#interface UIView (viewRecursion)
- (NSMutableArray*) allSubViews;
#end
// UIView+viewRecursion.m
#implementation UIView (viewRecursion)
- (NSMutableArray*)allSubViews
{
NSMutableArray *arr=[[[NSMutableArray alloc] init] autorelease];
[arr addObject:self];
for (UIView *subview in self.subviews)
{
[arr addObjectsFromArray:(NSArray*)[subview allSubViews]];
}
return arr;
}
#end
Usage : Now you should be looping through all the sub views and manipulate them as needed.
//disable all text fields
for(UIView *v in [self.view allSubViews])
{
if([v isKindOfClass:[UITextField class]])
{
((UITextField*)v).enabled=NO;
}
}
Here's another Swift implementation:
extension UIView {
var allSubviews: [UIView] {
return self.subviews.flatMap { [$0] + $0.allSubviews }
}
}
A solution in Swift 3 that gives all subviews without including the view itself:
extension UIView {
var allSubViews : [UIView] {
var array = [self.subviews].flatMap {$0}
array.forEach { array.append(contentsOf: $0.allSubViews) }
return array
}
}
I tag everything when it's created. Then it's easy to find any subview.
view = [aView viewWithTag:tag];
Just found an interesting way to do this through the debugger:
http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/
references this Apple Technote:
https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT
Just make sure your debugger is paused (either set a break point of pause it manually) and you can ask for the recursiveDescription.
Here is an example with actual view looping and breaking functionality.
Swift:
extension UIView {
func loopViewHierarchy(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
var stop = false
block(self, &stop)
if !stop {
self.subviews.forEach { $0.loopViewHierarchy(block: block) }
}
}
}
Call example:
mainView.loopViewHierarchy { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Reversed looping:
extension UIView {
func loopViewHierarchyReversed(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
for i in stride(from: self.highestViewLevel(view: self), through: 1, by: -1) {
let stop = self.loopView(view: self, level: i, block: block)
if stop {
break
}
}
}
private func loopView(view: UIView, level: Int, block: (_ view: UIView, _ stop: inout Bool) -> ()) -> Bool {
if level == 1 {
var stop = false
block(view, &stop)
return stop
} else if level > 1 {
for subview in view.subviews.reversed() {
let stop = self.loopView(view: subview, level: level - 1, block: block)
if stop {
return stop
}
}
}
return false
}
private func highestViewLevel(view: UIView) -> Int {
var highestLevelForView = 0
for subview in view.subviews.reversed() {
let highestLevelForSubview = self.highestViewLevel(view: subview)
highestLevelForView = max(highestLevelForView, highestLevelForSubview)
}
return highestLevelForView + 1
}
}
Call example:
mainView.loopViewHierarchyReversed { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Objective-C:
typedef void(^ViewBlock)(UIView* view, BOOL* stop);
#interface UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block;
#end
#implementation UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block {
BOOL stop = NO;
if (block) {
block(self, &stop);
}
if (!stop) {
for (UIView* subview in self.subviews) {
[subview loopViewHierarchy:block];
}
}
}
#end
Call example:
[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
if ([view isKindOfClass:[UIButton class]]) {
/// use the view
*stop = YES;
}
}];
With the help of Ole Begemann. I added a few lines to incorporate the block concept into it.
UIView+HierarchyLogging.h
typedef void (^ViewActionBlock_t)(UIView *);
#interface UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction;
#end
UIView+HierarchyLogging.m
#implementation UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction {
//view action block - freedom to the caller
viewAction(self);
for (UIView *subview in self.subviews) {
[subview logViewHierarchy:viewAction];
}
}
#end
Using the HierarchyLogging category in your ViewController. You are now have freedom to what you need to do.
void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
if ([view isKindOfClass:[UIButton class]]) {
NSLog(#"%#", view);
}
};
[self.view logViewHierarchy: ViewActionBlock];
Need not create any new function. Just do it when debugging with Xcode.
Set a breakpoint in a view controller, and make the app pause at this breakpoint.
Right click the empty area and press "Add Expression..." in Xcode's Watch window.
Input this line:
(NSString*)[self->_view recursiveDescription]
If the value is too long, right click it and choose "Print Description of ...". You will see all subviews of self.view in the console window. Change self->_view to something else if you don't want to see subviews of self.view.
Done! No gdb!
Here is a recursive code:-
for (UIView *subViews in yourView.subviews) {
[self removSubviews:subViews];
}
-(void)removSubviews:(UIView *)subView
{
if (subView.subviews.count>0) {
for (UIView *subViews in subView.subviews) {
[self removSubviews:subViews];
}
}
else
{
NSLog(#"%i",subView.subviews.count);
[subView removeFromSuperview];
}
}
By the way, I made an open source project to help with this sort of task. It's really easy, and uses Objective-C 2.0 blocks to execute code on all views in a hierarchy.
https://github.com/egold/UIViewRecursion
Example:
-(void)makeAllSubviewsGreen
{
[self.view runBlockOnAllSubviews:^(UIView *view) {
view.backgroundColor = [UIColor greenColor];
}];
}
Here is a variation on Ole Begemann's answer above, which adds indentation to illustrate the hierarchy:
// UIView+HierarchyLogging.h
#interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces;
#end
// UIView+HierarchyLogging.m
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces {
if (whiteSpaces == nil) {
whiteSpaces = [NSString string];
}
NSLog(#"%#%#", whiteSpaces, self);
NSString *adjustedWhiteSpaces = [whiteSpaces stringByAppendingFormat:#" "];
for (UIView *subview in self.subviews) {
[subview logViewHierarchy:adjustedWhiteSpaces];
}
}
#end
The code posted in this answer traverses all windows and all views and all of their subviews. It was used to dump a printout of the view hierarchy to NSLog but you can use it as a basis for any traversal of the view hierarchy. It uses a recursive C function to traverse the view tree.
I wrote a category some time back to debug some views.
IIRC, the posted code is the one that worked. If not, it will point you in the right direction. Use at own risk, etc.
This displays the hierarchy level as well
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
NSLog(#"%d - %#", level, self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy:(level+1)];
}
}
#end
Wish I'd found this page first, but if (for some reason) you want to do this non-recursively, not in a Category, and with more lines of code
I think all of the answers using recursion (except for the debugger option) used categories. If you don't need/want a category, you can just use a instance method. For instance, if you need to get an array of all labels in your view hierarchy, you could do this.
#interface MyViewController ()
#property (nonatomic, retain) NSMutableArray* labelsArray;
#end
#implementation MyViewController
- (void)recursiveFindLabelsInView:(UIView*)inView
{
for (UIView *view in inView.subviews)
{
if([view isKindOfClass:[UILabel class]])
[self.labelsArray addObject: view];
else
[self recursiveFindLabelsInView:view];
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.labelsArray = [[NSMutableArray alloc] init];
[self recursiveFindLabelsInView:self.view];
for (UILabel *lbl in self.labelsArray)
{
//Do something with labels
}
}
The method below creates one or more mutable arrays, then loops through the subviews of the input view. In doing so it adds the initial subview then queries as to whether there are any subviews of that subview. If true, it calls itself again. It does so until the all the views of the hierarchy have been added.
-(NSArray *)allSubs:(UIView *)view {
NSMutableArray * ma = [NSMutableArray new];
for (UIView * sub in view.subviews){
[ma addObject:sub];
if (sub.subviews){
[ma addObjectsFromArray:[self allSubs:sub]];
}
}
return ma;
}
Call using:
NSArray * subviews = [self allSubs:someView];