How to Refer to a Subview Button of Custom UIView in UITableViewController - ios

I'm facing problems with UITableViewCell being reused, and I am receiving suggestions to make the action of a UIButton occur within the UITableViewController.
The problem is that because the UIButton is instantiated within the custom UIView subclass, I cannot find a way to refer to it.
The code is:
class StarButton: UIView {
var buttonRow : Int = 0
var buttonSelected : Bool = false
override init (frame : CGRect)
{
super.init(frame : frame)
initStar()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
initStar()
}
func initStar(){
let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")
let button = UIButton(frame: CGRect(x: 2, y: 2, width: 33, height: 33))
button.userInteractionEnabled = true
button.addTarget(self, action: #selector(StarButton.fillingStar(_:)), forControlEvents: UIControlEvents.TouchUpInside)
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
if buttonSelected == true{
button.selected = true
}
addSubview(button)
}
//Could have various errors
func fillingStar(sender: UIButton){
if (sender.selected) == false{
FavoritesManager.favoritesList.setObject(ChemQuizCollection.wordListObject.quizList[buttonRow], forKey: "\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
} else{
FavoritesManager.favoritesList.removeObjectForKey("\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
for view in subviews
{
view.touchesBegan(touches, withEvent: event)
}
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
{
return true
}
}
This is the custom UIView subclass. I want to refer to the "button" in func initStar().
Using
for subview in instantiatedStarButton.subviews{
subview.selected = true
}
throws an error saying UIView doesn't have member "selected". Is there a better way?
Thank you all in advance.

The problem is with your code
StarButton.subviews{ subview.selected = true }
.subviews give UIView not UIButton: UIView has no .selected property
What should be done is following code or something similar. You have to cast tour UIView to UIButton
let filtured = instantiatedStarButton.subviews.filter({
if let button = $0 as? UIButton{
return button.selected
}
return false
})

buttonSelected is set to false while you are only changing the state of the button if its true. That's why you're getting the error.
You can either change the buttonSelected to true, or even better assign a tag to the button and compare that tag with the 'sender' in fillingStar.

Related

Swift 4 UIButton action in UIImageView not working

I am trying to add a delete button as a subview in an image. This is my current structure:
-> class DesignViewController: UIViewController
|
-> class Sticker: UIImageView, UIGestureRecognizerDelegate
|
-> UI button inside the Sticker
Inside Sticker class I have :
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let button2 = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button2.backgroundColor = .red
button2.setTitle("Delete", for: .normal)
button2.tag = 23
button2.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.addSubview(button2)
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
The buttonAction is not getting called.
When I change self.addSubview(button2) line to :
self.superview?.addSubview(button2)
I can see buttonAction getting called. However I would like to keep the button inside the Sticker view so that when user moves the sticker, the button moves as a subview with it.
Can anyone please help and let me know how I can keep the button inside Sticker view?
By default isUserInteractionEnabled property of UIImageView is set to false. Set it to true and your button will start to respond. You can set it in code as well as in the storyboards.
Also try setting the clipsToBounds property of your imageview to true. It will clip your button if it is going outside of the image bounds. That might be one of the reason that your button is not getting touches.
You should create a protocol delegate for button action. This is code example:
protocol ButtonDelegate: class {
func buttonTapped(button: UIButton)
}
class Sticker: UIImageView {
weak var delegate: ButtonDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button2)
}
lazy var button2: UIButton = {
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button2.backgroundColor = .red
button2.setTitle("Delete", for: .normal)
button2.tag = 23
button2.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
#objc func buttonAction(sender: UIButton) {
guard let delegate = delegate else { return }
delegate.buttonTapped(button: sender)
}
So, now go to your DesignViewControllerl, add your custom imageview class Sticker. Don't forget to do that "imageView.delegate = self". Then in extension add protocol delegate you've created before. Code example:
class DesignViewController: UIViewController {
private lazy var sticker: Sticker = {
let iv = Sticker(frame: view.bounds)
iv.delegate = self
return iv
}()
override viewDidLoad() {
super.viewDidLoad()
view.addSubiew(sticker)
}
}
extension DesignViewController: ButtonDelegate {
func buttonTapped(button: UIButton) {
// input your action here
}
}

Why touchesBegan listener not called(Swift 4)?

I created simple button - class Btn: UIControl and then add to parent View. Inside my Btn class I have override func touchesBegan.
Why touchesBegan not callend when I tapped to the button?
As I think, my Btn extends UIControl and Btn have to call touchesBegan.
My code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add my button
let button = Btn()
view.addSubview(button)
}
}
class Btn: UIControl {
fileprivate let button: UIButton = {
let swapButton = UIButton()
let size = CGFloat(50)
swapButton.frame.size = CGSize(width: size, height: size)
swapButton.backgroundColor = UIColor.green
return swapButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Why it not called?
print("test")
}
}
Play around with the following code for playgrounds based on your code, and you will understand what's going on. While your control wants to process the touchesBegan callback, it has an UIButton inside of it, which consumes the event itself. In the example below, the inner button is taking one quarter of the Btn size (red part), if you click on it, the "test" text won't get printed. The rest of the Btn space (green part) does not contain any other view that would consume the touchesBegan, thus clicking there will print "test" text.
I recommend you to take a look at the responder chain.
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add my button
let button = Btn(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.addSubview(button)
}
}
class Btn: UIControl {
fileprivate let button: UIButton = {
let swapButton = UIButton()
let size = CGFloat(50)
swapButton.frame.size = CGSize(width: size, height: size)
swapButton.backgroundColor = UIColor.green
return swapButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button)
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Why it not called?
print("test")
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()

Modify class from another view controller

I'm using this code to customize UIButtons on my main ViewController and have placed it in it's own separate .swift file to be referenced wherever a UIButton exists. I want to be able to modify the properties of the MyCustomButton from within my main ViewController after a button is pressed.
For example, I press a button that's using the properties from MyCustomButton and its color changes.
I'm just getting into swift and am not greatly familiar with it's ins and outs quite yet. Any help is appreciated.
import Foundation
import UIKit
class MyCustomButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.cornerRadius = 25;
self.layer.borderColor = UIColor.blackColor().CGColor
self.layer.borderWidth = 3
self.backgroundColor = UIColor.redColor()
self.tintColor = UIColor.whiteColor()
self.setTitle("", forState: UIControlState.Normal)
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = NSTextAlignment.Center
}
}
You can try to override layoutSubviews method:
override func layoutSubviews() {
super.layoutSubviews()
if self.state == UIControlState.Highlighted {
// DO WHAT YOU WANT
} else {
// RESET TO NORMAL
}
}
KVO or delegate protocol (Swift) would work! Either of these would allow you to manipulate the properties of your UI elements when a specific action occurs.
If you want to create new class and change button default behaivour, you should override functions like this one
override func pressesBegan(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {
super.pressesBegan(presses, withEvent: event)
// add your implementation here
}
// func pressesCancelled
// func pressesChanged
// func pressesEnded
// etc.
But if you just want to change button properties on button tap, you can do it on button callback method:
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: UIButtonType.System) as UIButton
button.frame = CGRectMake(100, 100, 100, 100)
button.setTitle("My Button", forState: UIControlState.Normal)
button.addTarget(self, action: "someAction:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
}
func someAction(sender: UIButton) {
print("My Button tapped")
sender.setTitle("Now Tapped", forState: UIControlState.Normal)
}

Touch up and Touch down action for UIImageView

what i want to achieve is when user touch on UIImageView set Image1, when user lifts his finger set Image2.
i can only get UIGestureRecognizerState.Ended with this code
var tap = UITapGestureRecognizer(target: self, action: Selector("tappedMe:"))
imageView.addGestureRecognizer(tap)
imageView.userInteractionEnabled = true
func tappedMe(gesture: UITapGestureRecognizer)
{
if gesture.state == UIGestureRecognizerState.Began{
imageView.image=originalImage
dbgView.text="Began"
}else if gesture.state == UIGestureRecognizerState.Ended{
imageView.image=filteredImage
dbgView.text="Ended"
}else if gesture.state == UIGestureRecognizerState.Cancelled{
imageView.image=filteredImage
dbgView.text="Cancelled"
}else if gesture.state == UIGestureRecognizerState.Changed{
imageView.image=filteredImage
dbgView.text="Changed"
}
}
The UITapGestureRecognizer doesn't change it's state to .Began, but the UILongPressGestureRecognizer does. If you for some reason font want to override the touch callbacks directly you could use a UILongPressGestureRecognizer with a very short minimumPressDuration of like 0.1 to achieve the effect.
Example by #Daij-Djan:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var tap = UILongPressGestureRecognizer(target: self, action: Selector("pressedMe:"))
tap.minimumPressDuration = 0
self.view.addGestureRecognizer(tap)
self.view.userInteractionEnabled = true
}
func pressedMe(gesture: UITapGestureRecognizer) {
if gesture.state == .Began{
self.view.backgroundColor = UIColor.blackColor()
} else if gesture.state == .Ended {
self.view.backgroundColor = UIColor.whiteColor()
}
}
}
Here is the solution:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//began
}
}
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//end
}
}
super.touchesEnded(touches, with: event)
}
Note: Put this inside a UIView SubClass and add: userInteractionEnabled = true inside the init block
The best and most practical solution would be to embed your UIImageView within a UIControl subclass.
By default UIImageView has user interaction enabled. If you create a simple UIControl subclass you can easily add your image view into it and then use the following methods to achieve what you want:
let control = CustomImageControl()
control.addTarget(self, action: "imageTouchedDown:", forControlEvents: .TouchDown)
control.addTarget(self, action: "imageTouchedUp:", forControlEvents: [ .TouchUpInside, .TouchUpOutside ])
The advantages of doing it this way is that you get access to all of the different touch events saving you time from having to detect them yourself.
Depending on what you want to do, you could also override var highlighted: Bool or var selected: Bool to detect when the user is interacting with the image.
It's better to do it this way so that your user has a consistent user experience with all the controls in their app.
A simple implementation would look something like this:
final class CustomImageControl: UIControl {
let imageView: UIImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
// setup our image view
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
imageView.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
imageView.trailingAnchor.constraintEqualToAnchor(trailingAnchor).active = true
imageView.topAnchor.constraintEqualToAnchor(topAnchor).active = true
imageView.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let control = CustomImageControl()
control.translatesAutoresizingMaskIntoConstraints = false
control.imageView.image = ... // set your image here.
control.addTarget(self, action: "imageTouchedDown:", forControlEvents: .TouchDown)
control.addTarget(self, action: "imageTouchedUp:", forControlEvents: [ .TouchUpInside, .TouchUpOutside ])
view.addSubview(control)
control.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
control.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
}
#objc func imageTouchedDown(control: CustomImageControl) {
// pressed down on the image
}
#objc func imageTouchedUp(control: CustomImageControl) {
// released their finger off the image
}
}

How to create radio buttons and checkbox in swift (iOS)?

I am developing an app that allows to do survey. My layout is generated from XML based questions.
I need to create radio buttons (single choice) and checkboxes (multiple answers). I did not find anything useful for swift.
Does anyone have an idea?
Checkbox
You can create your own CheckBox control extending UIButton with Swift:
import UIKit
class CheckBox: UIButton {
// Images
let checkedImage = UIImage(named: "ic_check_box")! as UIImage
let uncheckedImage = UIImage(named: "ic_check_box_outline_blank")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet {
if isChecked == true {
self.setImage(checkedImage, for: UIControl.State.normal)
} else {
self.setImage(uncheckedImage, for: UIControl.State.normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action:#selector(buttonClicked(sender:)), for: UIControl.Event.touchUpInside)
self.isChecked = false
}
#objc func buttonClicked(sender: UIButton) {
if sender == self {
isChecked = !isChecked
}
}
}
And then add it to your views with Interface Builder:
Radio Buttons
Radio Buttons can be solved in a similar way.
For example, the classic gender selection Woman - Man:
import UIKit
class RadioButton: UIButton {
var alternateButton:Array<RadioButton>?
override func awakeFromNib() {
self.layer.cornerRadius = 5
self.layer.borderWidth = 2.0
self.layer.masksToBounds = true
}
func unselectAlternateButtons() {
if alternateButton != nil {
self.isSelected = true
for aButton:RadioButton in alternateButton! {
aButton.isSelected = false
}
} else {
toggleButton()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
unselectAlternateButtons()
super.touchesBegan(touches, with: event)
}
func toggleButton() {
self.isSelected = !isSelected
}
override var isSelected: Bool {
didSet {
if isSelected {
self.layer.borderColor = Color.turquoise.cgColor
} else {
self.layer.borderColor = Color.grey_99.cgColor
}
}
}
}
You can init your radio buttons like this:
override func awakeFromNib() {
self.view.layoutIfNeeded()
womanRadioButton.selected = true
manRadioButton.selected = false
}
override func viewDidLoad() {
womanRadioButton?.alternateButton = [manRadioButton!]
manRadioButton?.alternateButton = [womanRadioButton!]
}
For Radio Buttons and CheckBoxes there is nothing that comes built in.
You can implement Checkboxes easily yourself. You can set an uncheckedImage for your button for UIControlStateNormal and a checkedImage for your UIControlStateSelected. Now on tap, the button will change its image and alternate between checked and unchecked image.
To use radio buttons, you have to keep an Array for all the buttons that you want to behave as radio buttons. Whenever a button is pressed, you need to uncheck all other buttons in the array.
For radio buttons you can use SSRadioButtonsController
You can create a controller object and add buttons array to it like
var radioButtonController = SSRadioButtonsController()
radioButtonController.setButtonsArray([button1!,button2!,button3!])
The main principle is something like this here.
Swift 5, Checkbox with animation
NOTE:- if you want to remove the blue background while isSelected change the UIButton type from System to Custom
Check my Example for the Checkbox and Radio button
https://github.com/rashidlatif55/CheckBoxAndRadioButton
Create an outlet for the button
#IBOutlet weak var checkBoxOutlet:UIButton!{
didSet{
checkBoxOutlet.setImage(UIImage(named:"unchecked"), for: .normal)
checkBoxOutlet.setImage(UIImage(named:"checked"), for: .selected)
}
}
Create an extension of UIButton
extension UIButton {
//MARK:- Animate check mark
func checkboxAnimation(closure: #escaping () -> Void){
guard let image = self.imageView else {return}
UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveLinear, animations: {
image.transform = CGAffineTransform(scaleX: 0.85, y: 0.85)
}) { (success) in
UIView.animate(withDuration: 0.1, delay: 0, options: .curveLinear, animations: {
self.isSelected = !self.isSelected
//to-do
closure()
image.transform = .identity
}, completion: nil)
}
}
}
How to use
#IBAction func checkbox(_ sender: UIButton){
sender.checkboxAnimation {
print("I'm done")
//here you can also track the Checked, UnChecked state with sender.isSelected
print(sender.isSelected)
}
}
Check out DLRadioButton. You can add and customize radio buttons directly from the Interface Builder. Also works with Swift perfectly.
Update: version 1.3.2 added square buttons, also improved performance.
Update: version 1.4.4 added multiple selection option, can be used as checkbox as well.
Update: version 1.4.7 added RTL language support.
Solution for Radio Button in Swift 4.2 without using third-party libraries
Create RadioButtonController.swift file and place following code in it:
import UIKit
class RadioButtonController: NSObject {
var buttonsArray: [UIButton]! {
didSet {
for b in buttonsArray {
b.setImage(UIImage(named: "radio_off"), for: .normal)
b.setImage(UIImage(named: "radio_on"), for: .selected)
}
}
}
var selectedButton: UIButton?
var defaultButton: UIButton = UIButton() {
didSet {
buttonArrayUpdated(buttonSelected: self.defaultButton)
}
}
func buttonArrayUpdated(buttonSelected: UIButton) {
for b in buttonsArray {
if b == buttonSelected {
selectedButton = b
b.isSelected = true
} else {
b.isSelected = false
}
}
}
}
Use it as below in your view controller file:
import UIKit
class CheckoutVC: UIViewController {
#IBOutlet weak var btnPaytm: UIButton!
#IBOutlet weak var btnOnline: UIButton!
#IBOutlet weak var btnCOD: UIButton!
let radioController: RadioButtonController = RadioButtonController()
override func viewDidLoad() {
super.viewDidLoad()
radioController.buttonsArray = [btnPaytm,btnCOD,btnOnline]
radioController.defaultButton = btnPaytm
}
#IBAction func btnPaytmAction(_ sender: UIButton) {
radioController.buttonArrayUpdated(buttonSelected: sender)
}
#IBAction func btnOnlineAction(_ sender: UIButton) {
radioController.buttonArrayUpdated(buttonSelected: sender)
}
#IBAction func btnCodAction(_ sender: UIButton) {
radioController.buttonArrayUpdated(buttonSelected: sender)
}
}
Be sure to add radio_off and radio_on images in Assets.
Result:
There's a really great library out there you can use for this (you can actually use this in place of UISwitch): https://github.com/Boris-Em/BEMCheckBox
Setup is easy:
BEMCheckBox *myCheckBox = [[BEMCheckBox alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
[self.view addSubview:myCheckBox];
It provides for circle and square type checkboxes
And it also does animations:
shorter ios swift 4 version:
#IBAction func checkBoxBtnTapped(_ sender: UIButton) {
if checkBoxBtn.isSelected {
checkBoxBtn.setBackgroundImage(#imageLiteral(resourceName: "ic_signup_unchecked"), for: .normal)
} else {
checkBoxBtn.setBackgroundImage(#imageLiteral(resourceName: "ic_signup_checked"), for:.normal)
}
checkBoxBtn.isSelected = !checkBoxBtn.isSelected
}
A very simple checkbox control.
#IBAction func btn_box(sender: UIButton) {
if (btn_box.selected == true)
{
btn_box.setBackgroundImage(UIImage(named: "box"), forState: UIControlState.Normal)
btn_box.selected = false;
}
else
{
btn_box.setBackgroundImage(UIImage(named: "checkBox"), forState: UIControlState.Normal)
btn_box.selected = true;
}
}
For a checkbox, you don't need to subclass the UIButton. It already has the isSelected property to handle this.
checkbox = UIButton.init(type: .custom)
checkbox.setImage(UIImage.init(named: "iconCheckboxOutlined"), for: .normal)
checkbox.setImage(UIImage.init(named: "iconCheckboxFilled"), for: .selected)
checkbox.addTarget(self, action: #selector(self.toggleCheckboxSelection), for: .touchUpInside)
Then in the action method toggle it's isSelected state.
#objc func toggleCheckboxSelection() {
checkbox.isSelected = !checkbox.isSelected
}
Steps to Create Radio Button
BasicStep : take Two Button. set image for both like selected and unselected.
than add action to both button.
now start code
1)Create variable :
var btnTag : Int = 0
2)In ViewDidLoad Define :
btnTag = btnSelected.tag
3)Now In Selected Tap Action :
#IBAction func btnSelectedTapped(sender: AnyObject) {
btnTag = 1
if btnTag == 1 {
btnSelected.setImage(UIImage(named: "icon_radioSelected"), forState: .Normal)
btnUnSelected.setImage(UIImage(named: "icon_radioUnSelected"), forState: .Normal)
btnTag = 0
}
}
4)Do code for UnCheck Button
#IBAction func btnUnSelectedTapped(sender: AnyObject) {
btnTag = 1
if btnTag == 1 {
btnUnSelected.setImage(UIImage(named: "icon_radioSelected"), forState: .Normal)
btnSelected.setImage(UIImage(named: "icon_radioUnSelected"), forState: .Normal)
btnTag = 0
}
}
Radio Button is Ready for you
You can simply subclass UIButton and write your own drawing code to suit your needs. I implemented a radio button like that of android using the following code. It can be used in storyboard as well.See example in Github repo
import UIKit
#IBDesignable
class SPRadioButton: UIButton {
#IBInspectable
var gap:CGFloat = 8 {
didSet {
self.setNeedsDisplay()
}
}
#IBInspectable
var btnColor: UIColor = UIColor.green{
didSet{
self.setNeedsDisplay()
}
}
#IBInspectable
var isOn: Bool = true{
didSet{
self.setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
self.contentMode = .scaleAspectFill
drawCircles(rect: rect)
}
//MARK:- Draw inner and outer circles
func drawCircles(rect: CGRect){
var path = UIBezierPath()
path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: rect.width, height: rect.height))
let circleLayer = CAShapeLayer()
circleLayer.path = path.cgPath
circleLayer.lineWidth = 3
circleLayer.strokeColor = btnColor.cgColor
circleLayer.fillColor = UIColor.white.cgColor
layer.addSublayer(circleLayer)
if isOn {
let innerCircleLayer = CAShapeLayer()
let rectForInnerCircle = CGRect(x: gap, y: gap, width: rect.width - 2 * gap, height: rect.height - 2 * gap)
innerCircleLayer.path = UIBezierPath(ovalIn: rectForInnerCircle).cgPath
innerCircleLayer.fillColor = btnColor.cgColor
layer.addSublayer(innerCircleLayer)
}
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.nativeScale
}
/*
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isOn = !isOn
self.setNeedsDisplay()
}
*/
override func awakeFromNib() {
super.awakeFromNib()
addTarget(self, action: #selector(buttonClicked(sender:)), for: UIControl.Event.touchUpInside)
isOn = false
}
#objc func buttonClicked(sender: UIButton) {
if sender == self {
isOn = !isOn
setNeedsDisplay()
}
}
}
I made a super simple class to handle this in a Mac application I'm working on. Hopefully, this is helpful to someone
RadioButtonController Class:
class RadioButtonController: NSObject {
var buttonArray : [NSButton] = []
var currentleySelectedButton : NSButton?
var defaultButton : NSButton = NSButton() {
didSet {
buttonArrayUpdated(buttonSelected: self.defaultButton)
}
}
func buttonArrayUpdated(buttonSelected : NSButton) {
for button in buttonArray {
if button == buttonSelected {
currentleySelectedButton = button
button.state = .on
} else {
button.state = .off
}
}
}
}
Implementation in View Controller:
class OnboardingDefaultLaunchConfiguration: NSViewController {
let radioButtonController : RadioButtonController = RadioButtonController()
#IBOutlet weak var firstRadioButton: NSButton!
#IBOutlet weak var secondRadioButton: NSButton!
#IBAction func folderRadioButtonSelected(_ sender: Any) {
radioButtonController.buttonArrayUpdated(buttonSelected: folderGroupRadioButton)
}
#IBAction func fileListRadioButtonSelected(_ sender: Any) {
radioButtonController.buttonArrayUpdated(buttonSelected: fileListRadioButton)
}
override func viewDidLoad() {
super.viewDidLoad()
radioButtonController.buttonArray = [firstRadioButton, secondRadioButton]
radioButtonController.defaultButton = firstRadioButton
}
}
For checkboxes there is actually a built-in solution in the form of UITableViewCell accessories. You can set up your form as a UITableView in which each cell as a selectable option and use accessoryType to set a check mark for selected items.
Here is a pseudo-code example:
let items = [SelectableItem]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get the item for the current row
let item = self.items[indexPath.row]
// ...dequeue and set up the `cell` as you wish...
// Use accessoryType property to mark the row as checked or not...
cell.accessoryType = item.selected ? .checkmark : .none
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Unselect row
tableView.deselectRow(at: indexPath, animated: false)
// Toggle selection
let item = self.items[indexPath.row]
item.selected = !item.selected
tableView.reloadData()
}
Radio buttons however do require a custom implementation, see the other answers.
The decision of checking or unchecking the checkbox button is something out of the scope of the view. View itself should only take care of drawing the elements, not deciding about the internal state of that. My suggested implementation is as follows:
import UIKit
class Checkbox: UIButton {
let checkedImage = UIImage(named: "checked")
let uncheckedImage = UIImage(named: "uncheked")
var action: ((Bool) -> Void)? = nil
private(set) var isChecked: Bool = false {
didSet{
self.setImage(
self.isChecked ? self.checkedImage : self.uncheckedImage,
for: .normal
)
}
}
override func awakeFromNib() {
self.addTarget(
self,
action:#selector(buttonClicked(sender:)),
for: .touchUpInside
)
self.isChecked = false
}
#objc func buttonClicked(sender: UIButton) {
if sender == self {
self.action?(!self.isChecked)
}
}
func update(checked: Bool) {
self.isChecked = checked
}
}
It can be used with Interface Builder or programmatically. The usage of the view could be as the following example:
let checkbox_field = Checkbox(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
checkbox_field.action = { [weak checkbox_field] checked in
// any further checks and business logic could be done here
checkbox_field?.update(checked: checked)
}
I don't have enough reputation to comment, so I'll leave my version of Salil Dwahan's version here. Works for Swift 5, XCode 11.3.
First place your button on IB, select type "Custom" and create an outlet and an action with the Assistant Layout (Ctrl + Drag). Include the following code and it should end like this:
class YourViewController: UIViewController {
#IBOutlet weak var checkbox: UIButton!
#IBAction func checkboxTapped(_ sender: UIButton) {
checkbox.isSelected = !checkbox.isSelected
}
override func viewDidLoad() {
super.viewDidLoad()
checkbox.setImage(UIImage.init(named: "checkMark"), for: .selected)
}
}
Don't forget to add the image to Assets and change the name to match!
checkbox.isSelected is the way to check
Though some of the answers mention it rightly that we can use the Selected State to set an image for Selected state of the button, it won't work elegantly when the button has to have both image and text.
Like many, I ended by subclassing UIButton; however, added support for setting images from Interface Builder.
Below is my code:
import UIKit
class CustomCheckbox: UIButton {
#IBInspectable var defaultStateImage: UIImage? = nil {
didSet{
self.setNeedsDisplay()
}
}
#IBInspectable var selectedStateImage: UIImage? = nil {
didSet{
self.setNeedsDisplay()
}
}
#IBInspectable var gapPadding: CGFloat = 0 {
didSet{
self.setNeedsDisplay()
}
}
#IBInspectable var isChecked: Bool = false {
didSet{
self.setNeedsDisplay()
}
}
var defaultImageView: UIImageView? = nil
var selectedImageView: UIImageView? = nil
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
if(defaultStateImage != nil) {
defaultImageView = UIImageView(image: defaultStateImage)
defaultImageView?.translatesAutoresizingMaskIntoConstraints = false
addSubview(defaultImageView!)
let length = CGFloat(16)
titleEdgeInsets.left += length
NSLayoutConstraint.activate([
defaultImageView!.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: -gapPadding),
defaultImageView!.centerYAnchor.constraint(equalTo: self.titleLabel!.centerYAnchor, constant: 0),
defaultImageView!.widthAnchor.constraint(equalToConstant: length),
defaultImageView!.heightAnchor.constraint(equalToConstant: length)
])
}
if(selectedStateImage != nil) {
selectedImageView = UIImageView(image: selectedStateImage)
selectedImageView!.translatesAutoresizingMaskIntoConstraints = false
addSubview(selectedImageView!)
let length = CGFloat(16)
titleEdgeInsets.left += length
NSLayoutConstraint.activate([
selectedImageView!.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: -gapPadding),
selectedImageView!.centerYAnchor.constraint(equalTo: self.titleLabel!.centerYAnchor, constant: 0),
selectedImageView!.widthAnchor.constraint(equalToConstant: length),
selectedImageView!.heightAnchor.constraint(equalToConstant: length)
])
}
if defaultImageView != nil {
defaultImageView!.isHidden = isChecked
}
if selectedImageView != nil {
selectedImageView!.isHidden = !isChecked
}
self.addTarget(self, action: #selector(checkChanged(_:)), for: .touchUpInside)
}
#objc func checkChanged(_ btn : UIButton){
self.isChecked = !self.isChecked
if defaultImageView != nil {
defaultImageView!.isHidden = isChecked
}
if selectedImageView != nil {
selectedImageView!.isHidden = !isChecked
}
}
}
Create 2 buttons one as "YES" and another as "NO".
Create a BOOL property Ex: isNRICitizen = false
Give same button connection to both the buttons and set a tag
(Ex: Yes button - tag 10 and No button -tag 20)
#IBAction func btnAction(_ sender:UIButton) {
isNRICitizen = sender.tag == 10 ? true : false
isNRICitizen ? self.nriCitizenBtnYes.setImage(#imageLiteral(resourceName: "radioChecked"), for: .normal) : self.nriCitizenBtnYes.setImage(#imageLiteral(resourceName: "radioUnchecked"), for: .normal)
isNRICitizen ? self.nriCitizenBtnNo.setImage(#imageLiteral(resourceName: "radioUnchecked"), for: .normal) : self.nriCitizenBtnNo.setImage(#imageLiteral(resourceName: "radioChecked"), for: .normal)
}
Swift 5.0 Updated Simple RadioButton For Swift (No Library)
First set images to button One Checked and Second Unchecked.
Then Provide 2 Outlet Of RadioButton.
#IBOutlet weak var radioMale: UIButton!
#IBOutlet weak var radioFemale: UIButton!
Create IBAction With Both Button Action in One Method.
#IBAction func btnRadioTapped(_ sender: UIButton) {
radioMale.setImage(UIImage(named: "Unchecked"), for: .normal)
radioFemale.setImage(UIImage(named: "Unchecked"), for: .normal)
if sender.currentImage == UIImage(named: "Unchecked"){
sender.setImage(UIImage(named: "Checked"), for: .normal)
}else{
sender.setImage(UIImage(named: "Unchecked"), for: .normal)
}
}
Couldn't find an easy SwiftUI version in this thread so here is a quick component using SF symbols.
struct CheckBox: View {
private let checked = Image("checkmark.square.fill")
private let unChecked = Image("checkmark.square")
#State private var isChecked: Bool = false
var body: some View {
ZStack {
isChecked == false ? unChecked : checked
}.onTapGesture {
isChecked.toggle()
}
}
}
If you use Image when change state. Try this:
var nightButtonState: Bool = false {
didSet {
nightButtonState ? autoNightButton.setBackgroundImage(UIImage(named: "unchecked_icon"), for: .normal) : autoNightButton.setBackgroundImage(UIImage(named: "checked_icon"), for: .normal)
}
}
Button action:
#IBAction func autoNightButtonAction(_ sender: UIButton) {
self.nightButtonState.toggle()
}

Resources