Swift normal class cannot receive touch events from UIButton - ios

I'm trying to create a separate class for a group of buttons to handle a logic of only one button get selected at one time (Radio button)
The logic is not important here. I just cannot receive the touchUpIndside event when tapping on one of these buttons.
I set the target to self (the custom RadionButtonsController class) but this class cannot receive the event.
I tried to add UIResponder as a superclass. But still cannot receive the event in this class.
Here is my code:
import UIKit
class RadioButtonsController: UIResponder
{
var buttons = [UIButton]()
var selectedValue: String?
init(numberOfButtons: Int, titles: [String], values: [String])
{
guard titles.count == numberOfButtons && values.count == numberOfButtons else
{
fatalError("number of items in `titles` and `values` must equal to the `numberOfButtons`")
}
super.init()
for i in 0..<numberOfButtons
{
let button = UIButton(type: .system)
button.setTitle(titles[i], for: .normal)
button.backgroundColor = [UIColor.red, UIColor.blue, UIColor.gray, UIColor.yellow].randomElement()
button.addTarget(self, action: #selector(radioButtonSelected(sender:)), for: .touchUpInside)
buttons.append(button)
}
}
#objc private func radioButtonSelected(sender: UIButton)
{
print("Selected Button: \(sender)") // this is will never get printed
}
}
And I use this buttons controller in a table view cell. In cellForRowAtIndexPath:
let itemOptionsCell = tableView.dequeueReusableCell(withIdentifier: "ItemOptionsCell", for: indexPath) as! ItemOptionsTableViewCell
let itemOption = logicController.item.options[indexPath.row]
itemOptionsCell.optionsTitleLabel.text = itemOption.name
let radioButtonsController = RadioButtonsController(numberOfButtons: itemOption.values.count,
titles: itemOption.values.map({ $0.name }),
values: itemOption.values.map({ $0.valueID }))
for button in radioButtonsController.buttons
{
itemOptionsCell.buttonsStackView.addArrangedSubview(button)
}
return itemOptionsCell
The buttons appear to be clicked, but the method radionButtonSelected(snder:) never get called.

The problem is that your code creates a RadioButtonsController object and then throws it away. Your buttons thus have no one to send their action to.

if you want to create a RadioButton without using any external pods
you can use this code :
import Foundation
import UIKit
class HRadioButton: UIButton {
#IBOutlet var otherButtons: [HRadioButton]!
var hSelected = false {
didSet {
if hSelected {
if imageView != nil{
self.setImage(checkedImage, for: .normal)
}else{
self.setImage(checkedImage, for: .normal)
print("image null")
}
if self.otherButtons != nil {
for button in self.otherButtons {
button.hSelected = false
button.imageView?.image = self.unCheckedImage
}
} else {
print("Button is null ")
}
}
}
}
var checkedImage: UIImage!, unCheckedImage: UIImage!
override func awakeFromNib() {
super.awakeFromNib()
checkedImage = #imageLiteral(resourceName: "ic_radio_button_checked").maskWithColor(color: tintColor)
unCheckedImage = #imageLiteral(resourceName: "ic_radio_button_unchecked").maskWithColor(color: tintColor)
setImage(unCheckedImage, for: .normal)
alignImageRight()
self.onTap {
self.imageView?.image = self.checkedImage
self.hSelected = true
if self.otherButtons != nil {
for button in self.otherButtons {
button.hSelected = false
button.imageView?.image = self.unCheckedImage
}
}
}
}
}
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskImage = cgImage!
let width = size.width
let height = size.height
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)!
context.clip(to: bounds, mask: maskImage)
context.setFillColor(color.cgColor)
context.fill(bounds)
if let cgImage = context.makeImage() {
let coloredImage = UIImage(cgImage: cgImage)
return coloredImage
} else {
return nil
}
}
}
extension UIView {
// In order to create computed properties for extensions, we need a key to
// store and access the stored property
fileprivate struct AssociatedObjectKeys {
static var tapGestureRecognizer = "MediaViewerAssociatedObjectKey_mediaViewer"
}
fileprivate typealias Action = (() -> Void)?
// Set our computed property type to a closure
fileprivate var tapGestureRecognizerAction: Action? {
set {
if let newValue = newValue {
// Computed properties get stored as associated objects
objc_setAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
get {
let tapGestureRecognizerActionInstance = objc_getAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer) as? Action
return tapGestureRecognizerActionInstance
}
}
// This is the meat of the sauce, here we create the tap gesture recognizer and
// store the closure the user passed to us in the associated object we declared above
public func onTap(action: (() -> Void)?) {
self.isUserInteractionEnabled = true
self.tapGestureRecognizerAction = action
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
self.addGestureRecognizer(tapGestureRecognizer)
}
// Every time the user taps on the UIImageView, this function gets called,
// which triggers the closure we stored
#objc fileprivate func handleTapGesture(sender: UITapGestureRecognizer) {
if let action = self.tapGestureRecognizerAction {
action?()
} else {
print("no action")
}
}
}
from xcode storyboard ui assign the class HRadioButton as the class of Button then you will find the otherButtons in Connection Inspector image of Connection Inspector Icon in Xcode
in xcode you will find otherButtons Appear there so drag and drop it to other buttons
and repeat this process on all buttons you want make it as Radio Group
for example
i have A Button,B Button,C Button
I will assign class to A Button then drag and drop otherbuttons from Connection Inspector to B and C Buttons
then Assign The Class To B button and make otherButtons refere to A,C Buttons and so on.
Later you can get the selected Btn from hSelected Property

Related

How to create different instances of the RatingControl Class in Apple Developer Tutorial?

I need to create a very similar class of the RatingControl class that you make when you create the FoodTracker tutorial. Only difference with mine is that I need that class to be able to create different instances of the rating control, that vary with the types of images. So instead of just having my positivePointRatingButtons UIButton array, I need it to be able to instantiate either the negativePointRatingButtons or superPointRatingButtons as-well. I just dont want to make another whole class just for this purpose, and I am new so I figure Id get some help.
import UIKit
#IBDesignable class PointRatingControl: UIStackView {
//MARK: Properties
private var positivePointRatingButtons = [UIButton]()
private var negativePointRatingButtons = [UIButton]()
private var superPointRatingButtons = [UIButton]()
var rating = 0 {
didSet {
updatePointButtonSelectionStates()
}
}
#IBInspectable var circleType: Int = 1 {
didSet {
setupButtons()
}
}
#IBInspectable var circleSize: CGSize = CGSize(width: 30.0, height: 30.0) {
didSet {
setupButtons()
}
}
#IBInspectable var circleCount: Int = 10 {
didSet {
setupButtons()
}
}
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupButtons()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupButtons()
}
//MARK: Button Action
#objc func ratingButtonTapped(button: UIButton) {
guard let index = positivePointRatingButtons.index(of: button) else {
fatalError("The button, \(button), is not in the positiveRatingButtons array: \(positivePointRatingButtons)")
}
// Calculate the rating of the selected button
let selectedRating = index + 1
if selectedRating == rating {
// If the selected star represents the current rating, reset the rating to 0.
rating = 0
} else {
// Otherwise set the rating to the selected star
rating = selectedRating
}
}
//MARK: Private Methods
private func setupButtons() {
// Clear any existing buttons
for button in positivePointRatingButtons {
removeArrangedSubview(button)
button.removeFromSuperview()
}
positivePointRatingButtons.removeAll()
// Load button images, since its #IDDesignable in order to show in the interface builder you have to specifyexplicitly the catalog's bundle, as opposed to just using UIImage(named:) method
let bundle = Bundle(for: type(of: self))
let emptyCircle = UIImage(named: "greenCirclePhoto", in: bundle, compatibleWith: self.traitCollection)
let selectedCircle = UIImage(named: "greenFilledCirclePhoto", in: bundle, compatibleWith: self.traitCollection)
let highlightedCircle = UIImage(named: "greenSelectedCirclePhoto", in: bundle, compatibleWith: self.traitCollection)
for _ in 0..<circleCount {
let button = UIButton()
button.setImage(emptyCircle, for: .normal)
button.setImage(selectedCircle, for: .selected)
button.setImage(highlightedCircle, for: .highlighted)
button.setImage(highlightedCircle, for: [.highlighted, .selected])
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: circleSize.height).isActive = true
button.widthAnchor.constraint(equalToConstant: circleSize.width).isActive = true
button.addTarget(self, action: #selector(PointRatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
addArrangedSubview(button)
positivePointRatingButtons.append(button)
}
updatePointButtonSelectionStates()
}
private func updatePointButtonSelectionStates() {
for (index, button) in positivePointRatingButtons.enumerated() {
// If the index of a button is less than the rating, that button should be selected.
button.isSelected = index < rating
}
}
}
Id like to be able to use #IBInspectable aswell using the circleType property I defined so that I can use like 1, 2, 3 Integers as representations for each case.
I figured out how to do it. I just made a switch case to load up different images based on the ratingType variable

Printing Contents of TableView in Swift IOS Application

I am making an app with a scene that contains a tableview. Each cell in the table view contains a rating control (made up of 5 stars) and a label. At the click of a button I would like to print all of the labels as well as the number of stars that user has clicked from the rating controls from the entire table view. to the console.
How can I do this?
Here is my tableview(_:cellForRowAt:) method
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell
// Table view cells are reused and should be dequeued using a cell identifier
let cellId = "cell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? MatchingTableViewCell else {
fatalError("The dequeued cell is not an instacne of MatchingTableViewCell")
}
// Fetches the appropriate match for the data source layout.
let match = matching[indexPath.row]
cell.nameLabel.text = match.name
cell.photoImagView.image = match.photo
cell.ratingControl.rating = match.rating
return cell
}
Data model object is an array of structs of Match objects:
import Foundation
import UIKit
import os.log
class Match: NSObject, NSCoding {
// MARK: Properties
var name: String
var photo: UIImage?
var rating: Int
// MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first
static let ArchiveURL = DocumentsDirectory?.appendingPathComponent("matching")
// MARK: Types
struct PropertyKey {
static let name = "name"
static let photo = "photo"
static let rating = "rating"
}
init?(name: String, photo: UIImage?, rating: Int) {
// The name must not be empty
guard !name.isEmpty else{
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
// Initialize stored properties
self.name = name
self.photo = photo
self.rating = rating
}
override var description : String {
return "rating \(self.rating) \n"
}
// MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(photo, forKey: PropertyKey.photo)
aCoder.encode(rating, forKey: PropertyKey.rating)
}
required convenience init?(coder aDecoder: NSCoder) {
// The name is required if we cannot decode a name string, the init should fail
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else{
os_log("Unable to decode the name for a Match object", log: OSLog.default, type: .debug)
return nil
}
// Because the photo is an optional property of Match, just use conditional cast.
let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage
let rating = aDecoder.decodeObject(forKey: PropertyKey.rating)
// Must call designated init
self.init(name: name, photo: photo, rating: rating as! Int)
}
}
RatingControl.swiift:
import UIKit
#IBDesignable class RatingControl: UIStackView {
// MARK: Properties
private var ratingButtons = [UIButton]()
var rating = 0 {
didSet {
updateButtonSelectionStates()
}
}
#IBInspectable var starSize: CGSize = CGSize(width: 44.0, height: 44.0) {// Defines size of buttons/
didSet{
setupButtons()
}
}
#IBInspectable var starCount: Int = 5 {// Defines number of buttons
didSet{
setupButtons()
}
}
// MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupButtons()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupButtons()
}
// MARK: Private Methods
private func setupButtons(){
// Clear any existing buttons
for button in ratingButtons{
removeArrangedSubview(button)
button.removeFromSuperview()
}
ratingButtons.removeAll()
// Load Button Images
let bundle = Bundle(for: type(of: self))
let filledStar = UIImage(named: "filledStar", in: bundle, compatibleWith: self.traitCollection)
let emptyStar = UIImage(named: "emptyStar", in: bundle, compatibleWith: self.traitCollection)
let highligtedStar = UIImage(named: "highlightedStar", in: bundle, compatibleWith: self.traitCollection)
for _ in 0..<starCount {
// Create the button
let button = UIButton()
// Set the button images
button.setImage(emptyStar, for: .normal)
button.setImage(filledStar, for: .selected)
button.setImage(highligtedStar, for: .highlighted)
button.setImage(highligtedStar, for: [.highlighted, .selected])
// Adding constraints
button.translatesAutoresizingMaskIntoConstraints = false // disables buttons automatically generated constraints
button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true // defines height
button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true // defines width
//Setup the button action
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
// Add button to stack
addArrangedSubview(button)
// Add the new button to the rating button Array
ratingButtons.append(button)
}
updateButtonSelectionStates()
}
// MARK: Button Action
#objc func ratingButtonTapped(button:UIButton){
guard let index = ratingButtons.index(of: button) else {
fatalError("The button, \(button), is not in the ratingButtons array: \(ratingButtons)")
}
// Calculate the rating of the selected button
let selectedRating = index + 1
if selectedRating == rating { // If the selected star represents the current rating, reset the rating to 0
rating = 0
} else{
// Otherwise set the rating to the selected star
rating = selectedRating
}
}
private func updateButtonSelectionStates() { // Update buttons appearance
for (index, button) in ratingButtons.enumerated() {
// If the index of a button is less than the rating, that button should be selected
button.isSelected = index < rating
}
}
}
You've got this wrong. Your table view does not save data, it displays it. You want to have a data model that holds the values you display from your table view. That's what feeds your tableView dataSource methods. Often it will be an array of structs.
You want to print the contents of your table view's data model.
Edit:
Now that you've provided that info we can help you.
Add CustomStringConvertible conformance to your data model:
class Match: NSObject, NSCoding, CustomStringConvertible {
The only requirement for CustomStringConvertible is that you provide a computed property description that's a String:
var description: String {
return "Match(name:\(name),rating:\(rating))"
}
Then in your button action
#IBAction func logInfo(sender: UIButton) {
matching.forEach { print($0) }
}
Since your Match class now conforms to CustomStringConvertible, you can print Match objects directly.
Or if you want indexes:
#IBAction func logInfo(sender: UIButton) {
matching.enumerated()forEach { print(String(format:"%02l", $0.0) + ": " + $0.1) }
}

UIButton not clickable when implemented from a class

I have a class ManageCell, which stores the frames, set the text of labels, etc... Which are the sub-views of an UIView CellView which is in the ViewController.
ManageCell:
import Foundation
import UIKit
class ManageCell {
var name: UILabel
var viewBelowButton: UIView
var deleteButton: UIButton
init (name: String) {
self.name = UILabel(frame: CGRectMake(10,15,250,40)
self.name.text = name
self.name.sizeToFit()
self.viewBelowButton = UIButton(frame: CGRectMake(UIScreen.mainScreen.bounds.width, 0, 70, 70)
//set outside the visible area so that it can be animated in.
self.viewBelowButton.backgroundColor = UIColor.redColor()
self.deleteButton = UIButton(frame: CGRectMake(0,0,70,70))
self.deleteButton.addTarget(ViewController.self, action: "deleteButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.deleteButton.setTitle("delete", forState: .Normal)
}
}
ViewController:
var cellView: [UIView] = []
var manageCells: [ManageCell] = []
...
//fill the manageCells array
func setSubViews () {
for (index, cell) in manageCells.enumerate() {
cellView.append(UIView(frame: CGRectMake(0, originY, view.bounds.width, 70)
cellView[index].addSubview(cell.name)
cellView[index].addSubview(cell.viewBelowButton)
cell.viewBelowButton.addSubview(cell.deleteButton)
}
}
func editing () {
var frame = CGRectMake(view.bound.width - 70, 0, 0, 70)
for cell in cells {
UIView.animateWithDuration(0.2, animations: {
cell.viewBelowButton.frame = frame
}
}
}
func deleteButtonPressed(sender: UIButton) {
print("button pressed")
}
User Interaction is enabled on both cellView[index], viewBelowButton and deleteButton.
The problem I'm facing is that the deleteButton does not respond to touches. The deleteButtonPressed: function is not being called.
code: https://github.com/an23lm/swift-stuff
I'm not sure if this is good practice, any suggestions are welcome.
Of course it's not called, ViewController.self is a class type, not your View Controller. And if even it was, it's not a good practice. You should use a delegate pattern here with some parameter to be returned back, so you will distinguish which cell delete button was pressed.
Example on your code:
protocol ManageCellDelegate: class {
func manageCellDeletePressed(id: Int)
}
class ManageCell {
var name: UILabel
var viewBelowButton: UIView
var deleteButton: UIButton
weak var delegate: ManageCellDelegate?
var id: Int
init (id: Int, name: String) {
self.id = id
self.name = UILabel(frame: CGRectMake(10,15,250,40))
self.name.text = name
self.name.sizeToFit()
self.viewBelowButton = UIButton(frame: CGRectMake(UIScreen.mainScreen().bounds.width, 0, 70, 70))
//set outside the visible area so that it can be animated in.
self.viewBelowButton.backgroundColor = UIColor.redColor()
self.deleteButton = UIButton(frame: CGRectMake(0,0,70,70))
self.deleteButton.addTarget(self, action: "deleteButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.deleteButton.setTitle("delete", forState: .Normal)
}
func deleteButtonPressed(sender: AnyObject) {
self.delegate?.manageCellDeletePressed(id)
}
}
class ViewController: UIViewController {
var cellView: [UIView] = []
var manageCells: [ManageCell] = []
func fullManageCells() {
for id in 0...15 {
let manageCell = ManageCell(id: id, name: "something")
manageCell.delegate = self
manageCells.append(manageCell)
}
}
}
extension ViewController: ManageCellDelegate {
func manageCellDeletePressed(id: Int) {
println("button with id \(id) pressed")
}
}

Button with constraints inside PersonCell hides half of the button Swift

Created a button inside a personCell which either shows Follow / Unfollow.
I've added the following constraints to the button:
Align Center Y to: Superview
Height = 43
Trailing space to: Superview
With or without the constraints it still cuts of a side of the button.
If the button was supposed to display "follow" it would only show "ow"
Code for PersonCell
class PersonCell: UITableViewCell {
#IBOutlet weak var followButton: UIButton!
var isFollowing: Bool?
var user: PFUser?
{
didSet
{
self.configure()
}
}
override func awakeFromNib()
{
super.awakeFromNib()
self.isFollowing = false
self.followButton?.hidden = true
}
override func prepareForReuse()
{
super.prepareForReuse()
self.isFollowing = false
self.followButton?.hidden = true
self.textLabel?.text = ""
self.user = nil
}
func configure()
{
if let constUser = user
{
self.textLabel?.text = constUser.username
// are we following this person?
NetworkManager.sharedInstance.isFollowing(constUser, completionHandler: {
(isFollowing, error) -> () in
if let _ = error
{
// Alert the user, or otherwise modify the UI
}
else
{
self.isFollowing = isFollowing
if isFollowing == true
{
let image = UIImage(named: "UnfollowButton")
self.followButton?.setImage(image, forState: .Normal)
}
else
{
let image = UIImage(named: "FollowButton")
self.followButton?.setImage(image, forState: .Normal)
}
self.followButton?.hidden = false
}
})
}
}
#IBAction func didTapFollow(sender: UIButton)
{
self.followButton?.enabled = false
let newValue = !(self.isFollowing == true)
NetworkManager.sharedInstance.updateFollowValue(newValue, user: self.user!) { (error) -> () in
self.followButton?.enabled = true
let image = (newValue == true) ? UIImage(named: "UnfollowButton") : UIImage(named: "FollowButton")
self.followButton?.setImage(image, forState: .Normal)
self.isFollowing = newValue
}
}
}
Im not yet allowed to post images but see this if you need to view how it looks:
http://postimg.org/image/jnf47mnr1/
Thankx in advance!

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