How to add custom UIButton programmatically swift - ios

I was able to create a custom checkbox button class. However, when I attempt to place the button in my view, I don't get an image. Setting the background color simply returns a square of that size. Here is my code for adding the box:
let box:CheckBox = CheckBox()
box.frame = CGRectMake(screenSize.width/2, screenSize.height/2, 200, 200)
self.view.addSubview(box)
I created the class as follows:
class CheckBox: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
//other stuff
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Images
let checkedImage = UIImage(named: "checked_checkbox")
let uncheckedImage = UIImage(named: "checkbox_unchecked_icon")
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(checkedImage, forState: .Normal)
} else {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}

Move your awakeFromNib code into your init().
awakeFromNib is only called when you're initialising from a nib/xib file.
This never gets called. You can test this by setting a breakpoint.
This is how it should look:
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
This way, self.isChecked gets called, an image is set, and you'll see more than a grey box.

Related

How to add custom uiButton Class checkbox to User defaults to save state? - Swift

So I'm creating an application that will need a checklist so I was able to create a custom checkbox class to make the checkbox and add that onto my uiButtons which work fine but upon opening up and building the app again it does not save state of the checkbox so how can I make it that the button input will be added to user defaults using this code so when I open the app again the checkbox input will have saved and be returned?
import UIKit
class CheckBox: UIButton {
//images
let checkedImage = UIImage(named: "checkedd_checkbox")
let unCheckedImage = UIImage(named: "uncheckedd_checkbox")
//bool propety
#IBInspectable var isChecked:Bool = false{
didSet{
self.updateImage()
}
}
override func awakeFromNib() {
self.addTarget(self, action: #selector(CheckBox.buttonClicked), for: UIControl.Event.touchUpInside)
self.imageView?.contentMode = .scaleAspectFit
self.imageEdgeInsets = UIEdgeInsets(top: 5.0, left: 0.0, bottom: 5.0, right: 50.0)
self.updateImage()
}
func updateImage() {
if isChecked == true{
self.setImage(checkedImage, for: [])
}else{
self.setImage(unCheckedImage, for: [])
}
}
#objc func buttonClicked(sender:UIButton) {
if(sender == self){
isChecked = !isChecked
}
}
}
Just set Userdefault in buttonClicked action like
#objc func buttonClicked(sender:UIButton) {
if(sender == self){
isChecked = !isChecked
UserDefaults.standard.set(isChecked, forKey: "give a spesific name here")
}
}
And check button is selected status in
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let getCheckStatus = UserDefaults.standard.bool(forKey: "given spesific name here")
self.isChecked = getCheckStatus
updateImage()
}
If you load this view from storyboard or xib use required init?(coder aDecoder: NSCoder) , If you add manually use override init(frame: CGRect) {... }

Custom UIButton #IBDesignable

I created a UIButton subclass that looks like a checkmark.
Here is the class:
import UIKit
#IBDesignable
class CheckedButton: UIButton {
// MARK: - Properties
#IBInspectable var checked: Bool = false {
didSet {
// Toggle the check/uncheck images
updateImage()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
updateImage()
self.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
private func updateImage() {
let image = checked ? UIImage(named: "checked") : UIImage(named: "unchecked")
self.setImage(image, for: .normal)
}
/// Called each time the button is tapped, and toggles the checked property
#objc private func tapped() {
checked = !checked
print("New value: \(checked)")
}
}
Since I set the checkedproperty as #IBInspectable, I see it in IB :
The weird thing is:
if I let this property as default, it is correctly showing in the storyboard
but if I choose either on or off inthe inspector, the screen is not updated properly.
As the class is marked #IBDesignable, I would expect the button appearance to update in IB according to the value set for this property in the inspector tab.
Got a clue?
UIimage(named:) method uses main bundle but Interface Builder load resources in different way.
Try this:
UIImage(named: "checked", in: bundle, compatibleWith: nil)
#IBDesignable
class CheckedButton: UIButton {
// MARK: - Properties
#IBInspectable var checked: Bool = false {
didSet {
// Toggle the check/uncheck images
updateImage()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
internal func setup() {
self.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
private func updateImage() {
let bundle = Bundle(for: CheckedButton.self)
let image = checked ? UIImage(named: "checked", in: bundle, compatibleWith:nil) : UIImage(named: "unchecked", in: bundle, compatibleWith:nil)
self.setBackgroundImage(image, for: .normal)
}
/// Called each time the button is tapped, and toggles the checked property
#objc private func tapped() {
checked = !checked
print("New value: \(checked)")
}
My suggestion is in using other custom function for getting image from assets:
extension UIImage {
public static func image(name: String, _ sender: UIView?) -> UIImage? {
#if TARGET_INTERFACE_BUILDER
if let sender = sender {
let bundle = Bundle(for: sender.classForCoder)
return UIImage(named: name, in: bundle, compatibleWith: sender.traitCollection)
} else {
return UIImage(named: name)
}
#else
return UIImage(named: name)
#endif
}
}
For your example:
private func updateImage() {
let image = checked ? UIImage.image(name: "checked", self) : UIImage(name: "unchecked", self)
setImage(image, for: .normal)
}
Also, UIButton class has checked state, which you can use without creation own sub class:
let btn = UIButton(type: .custom)
btn.setImage(UIImage.image(name: "unchecked", self), for: .normal)
btn.setImage(UIImage.image(name: "checked", self), for: .selected)
btn.isSelected = true // change button state between checked and unchecked

accessing global variable from programmatic uibutton property

I want to use the same UIView class to record and play sound. In this view class, I add a button programmatically and I want to change the button text according to the purpose of the view. When the view is created as a player it will show play icon (or the text for now). If the view is created as a recorder it will show the record icon. But I cannot access global variable in the view from my UIButton.
class AudioPlayerView: UIView {
var isRecorder = false
override init(frame: CGRect) {
super.init(frame: frame)
...
}
let theButton: UIButton = {
let button = UIButton()
if isRecorder {
button.setTitle("▷", for: .normal)
button.addTarget(self, action: #selector(playPauseAudio), for: .touchUpInside)
} else {
button.setTitle("▢", for: .normal)
button.addTarget(self, action: #selector(recordTapped), for: .touchUpInside)
}
button.backgroundColor = .clear
button.layer.cornerRadius = 15.0
return button
}()
I have re implemented your player class. Now you do not need to create two separate view for recording and playing status. All you need is to change the isRecorder value and your button title will be change without any need to create another view.
class AudioPlayerView: UIView {
var isRecorder = false {
didSet {
if isRecorder {
theButton.setTitle("▷", for: .normal)
} else {
theButton.setTitle("▢", for: .normal)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
if isRecorder {
theButton.setTitle("▷", for: .normal)
} else {
theButton.setTitle("▢", for: .normal)
}
//Add your button to view as subview.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let theButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.layer.cornerRadius = 15.0
button.addTarget(self, action: #selector(tapButton), for: .touchUpInside)
return button
}()
func tapButton() {
if isRecorder {
playPauseAudio()
} else {
recordTapped()
}
}
func playPauseAudio() {
}
func recordTapped() {
}
}
Actually, I made following in my viewcontroller class after I created the view. I simply set the button subview title and added relevant target. Now it seems to be functioning. But as a rookie, I am not sure if this is a good solution... Thanks every one who shared their comments and codes as well.
if cell.name == "recorder" {
audioPlayerView?.theButton.setTitle("▢", for: .normal)
audioPlayerView?.theButton.addTarget(audioPlayerView, action: #selector(audioPlayerView?.recordTapped), for: .touchUpInside)
} else {
audioPlayerView?.theButton.setTitle("▷", for: .normal)
audioPlayerView?.theButton.addTarget(audioPlayerView, action: #selector(audioPlayerView?.playPauseAudio), for: .touchUpInside)
}

Why Is My IBDesignable UIButton Subclass Not Rendering In Interface Builder?

In the screen shot below please note that the DropDownButton (the selected view) is not being live rendered. Also, please note the "Designables Up To Date" in the Identity Inspector. Finally, please note the two break points in the assistant editor: if I execute Editor -> Debug Selected Views then both of these break points are hit.
Here's what It looks like when I run it:
Here's the code:
#IBDesignable
class DropDownButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
if image(for: .normal) == nil {
//setImage(#imageLiteral(resourceName: "DropDown"), for: .normal)
let bundle = Bundle(for: DropDownButton.self)
if let image = UIImage(named: "DropDown", in: bundle, compatibleWith: nil) {
setImage(image, for: .normal) // Editor -> Debug Selected Views reaches this statement
}
}
if title(for: .normal) == nil {
setTitle("DropDown", for: .normal) // Editor -> Debug Selected Views reaches this statement
}
addTarget(self, action: #selector(toggle), for: .touchUpInside)
}
override func layoutSubviews() {
super.layoutSubviews()
if var imageFrame = imageView?.frame, var labelFrame = titleLabel?.frame {
labelFrame.origin.x = contentEdgeInsets.left
imageFrame.origin.x = labelFrame.origin.x + labelFrame.width + 2
imageView?.frame = imageFrame
titleLabel?.frame = labelFrame
}
}
override func setTitle(_ title: String?, for state: UIControlState) {
super.setTitle(title, for: state)
sizeToFit()
}
public var collapsed: Bool {
return imageView?.transform == CGAffineTransform.identity
}
public var expanded: Bool {
return !collapsed
}
private func collapse() {
imageView?.transform = CGAffineTransform.identity
}
private func expand() {
imageView?.transform = CGAffineTransform(rotationAngle: .pi)
}
#objc private func toggle(_: UIButton) {
if collapsed { expand() } else { collapse() }
}
}
First Edit:
I added the prepareForInterfaceBuilder method as per #DonMag's answer. Doing so made an improvement but there is still something wrong: Interface builder seems confused about the frame. When I select the button only the title is selected, not the image (i.e. triangle). I added a border; it goes around both the title and the image. Here is a picture:
If I drag the button to a new position then everything moves, title and image.
Also, it surprised me that prepareForInterfaceBuilder made a difference. My understanding of this method it that it allows me to do interface builder only setup such as providing dummy data.
Add this:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
initialize()
}

Saving state of UIButton (checkbox) - Swift

I'm a new developer and I'm making a to-do list app in Swift. I was able to figure out how to change the state of the UIButton (checkbox) on clicking. At the moment I'm trying to figure out how to save that state for when I exit the app or switch views and return. I was able to successfully save the text (to-do list tasks) by using NSUserDefaults and have also experimented with it on my buttons but cannot figure out how to get it to work.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var myButton: Qbutton!
override func viewDidLoad() {
super.viewDidLoad()
var isChecked = NSUserDefaults.standardUserDefaults().boolForKey("isBtnChecked") // here we obtain the last state of the button
myButton.isChecked = isChecked
self.view.addSubview(myButton)
I have the UIButton in a subclass of QButton.
class Qbutton: UIButton {
var isSelected: Bool = defaults.boolForKey("isButtonChecked")
// Images
let selectedImage = UIImage(named: "Selected")
let unselectedImage = UIImage(named: "Unselected")
//Bool Property
var isChecked:Bool = false{
didSet{
if isChecked == true{
self.setImage(selectedImage, forState: UIControlState.Normal)
}else{
self.setImage(unselectedImage, forState: UIControlState.Normal)
}
defaults.setObject(isChecked, forKey: "isButtonChecked")
}
}
override init(frame: CGRect){
super.init(frame:frame)
self.layer.masksToBounds = true
self.setImage(unselectedImage, forState: UIControlState.Normal)
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func buttonClicked(sender: UIButton) {
if(sender == self){
if isChecked == true{
isChecked = false
}else{
isChecked = true
}
}
}
}
well i think you can save the state of the UIButton with the NSUserDefaults just when the variable isChecked is changed. Something like this:
var isChecked:Bool = false{
didSet{
if isChecked {
self.setImage(selectedImage, forState: UIControlState.Normal)
}else{
self.setImage(unselectedImage, forState: UIControlState.Normal)
}
NSUserDefaults.standardUserDefaults().setObject(isChecked, forKey: "isBtnChecked")
// this method is automatically invoked at periodic intervals, but
//if you cannot wait for the automatic synchronization you can invoke it
NSUserDefaults.standardUserDefaults().synchronize()
}
}
Now when your app is launched you can check the last state of the UIButton with this:
var isSelected: Bool = NSUserDefaults.standardUserDefaults().boolForKey("isBtnChecked")
UPDATE
I made an example using your Qbutton class. I hope this can help you:
import Foundation
import UIKit
class Qbutton: UIButton {
// Images
let selectedImage = UIImage(named: "Selected")
let unselectedImage = UIImage(named: "Unselected")
//Bool Property
var isChecked:Bool = false{
didSet{
if isChecked {
self.setImage(selectedImage, forState: UIControlState.Normal)
}else{
self.setImage(unselectedImage, forState: UIControlState.Normal)
}
NSUserDefaults.standardUserDefaults().setObject(isChecked, forKey: "isBtnChecked")
// this method is automatically invoked at periodic intervals, but
//if you cannot wait for the automatic synchronization you can invoke it
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override init(frame: CGRect){
super.init(frame:frame)
self.layer.masksToBounds = true
self.setImage(unselectedImage, forState: UIControlState.Normal)
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func buttonClicked(sender: UIButton) {
if(sender == self){
if isChecked == true{
isChecked = false
self.setImage(unselectedImage, forState: UIControlState.Normal)
}else{
isChecked = true
self.setImage(selectedImage, forState: UIControlState.Normal)
}
}
}
}
Now in the UIViewController i create a Qbutton and before i add it to the UIViewController view i read the last state of the variable "isBtnChecked".
From the official docs:
If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, NO is returned.
import UIKit
class ViewController: UIViewController {
var myButton: Qbutton!
override func viewDidLoad() {
super.viewDidLoad()
myButton = Qbutton(frame: CGRectMake(100,100,50 ,50));
var isChecked = NSUserDefaults.standardUserDefaults().boolForKey("isBtnChecked") // here we obtain the last state of the button
myButton.isChecked = isChecked // we set the value
self.view.addSubview(myButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
UPDATE 2
Here is an example using the selected property of UIButton
import Foundation
import UIKit
class Qbutton: UIButton {
// Images
let selectedImage = UIImage(named: "Selected")
let unselectedImage = UIImage(named: "Unselected")
//Bool Property
override var selected: Bool{
didSet{
if selected {
self.setImage(selectedImage, forState: UIControlState.Normal)
}else{
self.setImage(unselectedImage, forState: UIControlState.Normal)
}
NSUserDefaults.standardUserDefaults().setObject(selected, forKey: "isBtnChecked")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override init(frame: CGRect){
super.init(frame:frame)
self.layer.masksToBounds = true
self.setImage(unselectedImage, forState: UIControlState.Normal)
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func buttonClicked(sender: UIButton) {
self.selected = !self.selected
}
}
And the UIViewController :
import UIKit
class ViewController: UIViewController {
var myButton: Qbutton!
override func viewDidLoad() {
super.viewDidLoad()
myButton = Qbutton(frame: CGRectMake(100,100,50 ,50));
myButton.selected = NSUserDefaults.standardUserDefaults().boolForKey("isBtnChecked") // here we obtain the last state of the button
self.view.addSubview(myButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I hope this really can help you!
I think you are completely over-thinking it and making it complicated. Since it's just a simple button the easiest way to do it is set a bool through an IBAction. Let me know if I'm missing something from your question though.
In Objective-C it would be something like this..
{
BOOL _isStateEnabled;
}
-(IBAction)clickedButton: (id)sender {
_isStateEnabled = !_isStateEnabled;
}
In Swift I'm not fluent but, something like this..
{
let _isStateEnabled as boolean;
}
func buttonClicked(sender: UIButton) {
_isStateEnabled = !_isStateEnabled;
}

Resources