35

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.

rkb
  • 10,933
  • 22
  • 76
  • 103
  • possible duplicate of [iPhone UIButton with UISwitch functionality](http://stackoverflow.com/questions/2255166/iphone-uibutton-with-uiswitch-functionality) – QED Feb 14 '13 at 19:19

11 Answers11

42

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.

Community
  • 1
  • 1
Lee Probert
  • 10,308
  • 8
  • 43
  • 70
  • 9
    Ignore all the stuff below and use Lee's link above. – Shaolo Apr 15 '13 at 10:13
  • Just worth pointing out that a button's `selected` state and `isSelected` property come from `UIControl`, and so work across all `UIControl`s. – wardw Apr 04 '19 at 15:37
15

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.

Dimitris
  • 13,480
  • 17
  • 74
  • 94
8

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];
}
Nav
  • 1,185
  • 16
  • 23
intropedro
  • 2,804
  • 1
  • 24
  • 25
7

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
        // ... 

    }
}
icodebuster
  • 8,890
  • 7
  • 62
  • 65
4

For Swift 3

@IBAction func playPause(sender : UIButton){


   if sender.isSelected {
      player.pause()
   }else{
      player.play()
  }
  sender.isSelected = !sender.isSelected
}
Vivek Goswami
  • 432
  • 3
  • 16
  • This code doesnt work as intended. When i tap the button for the first time, the button doesnt move into a selected state because it's being switched to false in the first line itself. This works as intended if the first line is added after the else statement. – Karthik Kannan Mar 22 '18 at 21:38
3
- (IBAction)buttonTapped:(UIButton *)sender {


   //first time sender.selected is No
    if (sender.selected) {
        //code here
        sender.selected=NO;
    }
    else{
    //code here
        sender.selected=YES;
    }
}
Dheeraj Kumar
  • 441
  • 3
  • 16
1

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

1

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)
    }
}
Stepan Novikov
  • 1,402
  • 12
  • 22
1

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)
  }
}
aprofromindia
  • 1,111
  • 1
  • 13
  • 27
0

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.

Morion
  • 10,495
  • 1
  • 24
  • 33
  • well yeah, but this way we are changing the image every time. Is there any way where we dont have to change it on every click and just assign it at the start. – rkb Nov 09 '09 at 16:56
  • I cannot understand why it so difficult to change image? and to your question, if there is any way to assign this behavior only once, I don't know it. – Morion Nov 09 '09 at 17:37
-1

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
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
  • Oh, toggle goes BOOL toggle(BOOL boolean) { return !boolean; } – Geri Borbás Sep 09 '12 at 16:36
  • I'd rewrite this using the UIButton's own state property. That is what it is for. See other answers. – n13 Mar 12 '14 at 17:05
  • "addTarget", seems like a bad option, in case you add an additional Target to monitor toggle state on tap of button. Then order of targets in which they are called will determine the toggle state. – Amit Apr 05 '16 at 09:57