accessing global variable from programmatic uibutton property - ios

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)
}

Related

Action of UIButton doesn't work if the button is initialized with let and the addTarget in it in custom view, but it works in UIViewController

When I create a button like below in the custom view the action does not work:
private let usernameButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("someTitle", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 13)
button.addTarget(self, action: #selector(didTapUsername), for: .touchUpInside)
return button
}()
However, it works when I do the same in UIViewController somehow.
It always works if I make it lazy var but couldn't understand why it doesn't work if I make it let in custom view while It is working in UIViewController
Thanks for any comment.
self inside that block actually is the block, not the view(you can see it using print(self)), probably because it's not yet initialised(same reason why you can't use self before super.init), that's why I usually add targets/delegates outside of the initialisation block. With lazy var self is already initialised, so no problem should be there.
I'm not sure why it works at all, seems kind of magic for me, and I wouldn't depend on it.
Anyway, it works fine with both custom ViewController and custom View in my case: I've added CustomView as subview to ViewController in the storyboard.
class ViewController: UIViewController {
private let usernameButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("someTitle", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 13)
button.addTarget(self, action: #selector(didTapUsername), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.insertSubview(usernameButton, at: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
usernameButton.frame = view.bounds
}
#objc
func didTapUsername() {
print("ViewController", #function)
}
}
class CustomView: UIView {
private let usernameButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("someTitle", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 13)
button.addTarget(self, action: #selector(didTapUsername), for: .touchUpInside)
return button
}()
override func awakeFromNib() {
super.awakeFromNib()
addSubview(usernameButton)
}
override func layoutSubviews() {
super.layoutSubviews()
usernameButton.frame = bounds
}
#objc
func didTapUsername() {
print("CustomView", #function)
}
}
This shouldn't do differently for UIViewController/UIView, check out your logic

swift cannot add target to UIButton

so this question is super simple, but i checked that like a 100 times but it still won't work.
Basically, when user taps on tableView cell, it open another VC with different views, depending whether or not user is owner of post.
First condition, works just fine, adding target to button, while when second is being executed, nothing happens
lazy var buttonsView = DetailButtonsView() // those are almost the same
lazy var addvertView = AdvertiseView()
// inside of buttonsView()
lazy var skipButton: UIButton = {
let button = UIButton()
button.setTitle("Пожаловаться", for: .normal)
button.setTitleColor(.mainColor, for: .normal)
button.titleLabel?.font = .getPoppinsMediumFont(on: 15)
return button
}()
override init(frame: CGRect) {
super.init(frame: .zero)
setupView() //set up constraints
}
//inside of AdvertiseView()
lazy var blackButton:UIButton = {
var button = UIButton()
button.layer.cornerRadius = 8
button.backgroundColor = .black
return button
}()
override init(frame: CGRect) {
super.init(frame: .zero)
setupView() //set up constraints
}
//
func setUpBottom() -> Void {
if dataInfo!.user_id! == UserSettings.userModel.id
{
self.backView.addSubview(addvertView) //also works
addvertView.snp.makeConstraints //works
{
(make) in
make.left.equalToSuperview()
make.top.equalTo(userView.snp.bottom).offset(24)
make.height.equalTo(450)
make.bottom.lessThanOrEqualTo(-34)
}
addvertView.blackButton.addTarget(self, action: #selector(blackButtonMethod), for: .touchUpInside) // does not add target
}
else {
backView.addSubview(buttonsView) //works
buttonsView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalTo(userView.snp.bottom).offset(24)
make.bottom.lessThanOrEqualTo(-34)
}
buttonsView.skipButton.addTarget(self, action: #selector(toComplain), for: .touchUpInside) //works
}
#objc func toComplain(){ //works
let vc = ComplaintTypeViewController()
vc.advertID = dataInfo!.id!
navigationController?.pushViewController(vc, animated: true)
}
#objc func blackButtonMethod(){ //does not work
print("hello")
parameters["action"] = "hot"
parameters["advert_id"] = String(describing: dataInfo!.id)
updateAdvert(parameters: parameters)
}
The target is probably added, but you cannot interact with it. That usually happens when constraints/frame is not set right. I see that the skip button uses:
button.setTitle("Пожаловаться", for: .normal)
which will infer autolayout width/height.
I don't see black blackButton's autolayout constraints or label set anywhere.

Adding a target to button inside a closure doesn't work

The following code is located inside a subclass of UIView
I am setting up a cancelButton inside a closure:
private var cancelButtonClosure: UIButton = {
...
button.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
}()
And at first I instantiated the button inside a function like so:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
...
addSubview(cancelButton)
...
}
However this resulted in the cancel function not being called at all (even though the layout was right and the button was highlighting)
So I made these change:
Removed the addTarget part from the cancelButtonClosure
Added the addTarget part inside the showConfirmationView function
So it looked like that:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
cancelButton.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
addSubview(cancelButton)
...
}
It worked: the cancel function was called; but I don't know why. I'm really curious to know why what I did before did not work. Thanks for your insights!
Check your implementation because a setup like this works as expected:
private var cancelButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Cancel", for: .normal)
btn.addTarget(self, action: #selector(cancelSomething(_:)), for: .touchUpInside)
return btn
}()
#objc func cancelSomething(_ sender: UIButton) {
print("Something has to be cancelled")
}
override func viewDidLoad() {
super.viewDidLoad()
showConfirmationView()
}
func showConfirmationView() {
cancelButton.sizeToFit()
cancelButton.center = view.center
view.addSubview(cancelButton)
}

UIButton addTarget Selector is not working

SquareBox.swift
class SquareBox {
func createBoxes() {
for _ in 0..<xy {
let button = UIButton()
button.backgroundColor = .white
button.setTitleColor(UIColor.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.black.cgColor
stack.addArrangedSubview(button)
button.addTarget(self, action: #selector(click(sender:)) , for: .touchUpInside)
}
}
#objc func click(sender : UIButton) {
print("Click")
}
}
ViewController.swift
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let boxRow = SquareBox()
boxRow.createBoxes()
}
}
Also I've tried #IBAction instead of #objc, it doesn't work, but if I use "click" function in ViewController.swift that I created this object, it's working but I need this function inside of this class.
Now that you have posted relevant information in your question, the problem is quite clear. You have a memory management issue.
In your GameViewController's viewDidLoad you create a local instance of SquareBox. This local instance goes out of scope at the end of viewDidLoad. Since there is no other reference to this instance, it gets deallocated at the end of viewDidLoad.
Since the instance of SquareBox has been deallocated, it is not around to act as the button's target. And your click method is never called.
The solution is to keep a reference in your view controller:
class GameViewController: UIViewController {
let boxRow = SquareBox()
override func viewDidLoad() {
super.viewDidLoad()
boxRow.createBoxes()
}
}
var btnfirst:UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
btnfirst = UIButton(type: .system)
btnfirst.setTitle("Press", for: .normal)
btnfirst.setTitleColor(.red, for: .normal)
btnfirst.frame = CGRect(x: 100, y: 200, width: 100, height: 30)
btnfirst.addTarget(self, action: #selector(benpress( sender:)),for: .touchUpInside)
self.view.addSubview(btnfirst)
}
func benpress( sender :UIButton)
{
//Your Code Here
}
For those who did not find a solution, here is mine.
If you constructed your UIButton as
let button: UIButton = {
return UIButton()
}()
Just convert those into
lazy var button: UIButton = {
return UIButton()
}()
I think this is because of somewhat deallocation as mentioned above.
button.addTarget(self, action:#selector(self.click), for: .touchUpInside)
func click(sender : UIButton) {
// code here
}
I guess the issue is how you are setting up layout of your buttons.
Try this:
func createBoxes() {
stack.backgroundColor = UIColor.red
for _ in 0..<xy {
// Create the button
let button = UIButton()
button.backgroundColor = UIColor.red
// Add constraints
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
// Setup the button action
button.addTarget(self, action: #selector(SquareBox.click(sender:)), for: .touchUpInside)
// Add the button to the stack
stack.addArrangedSubview(button)
}
}
#objc func click(sender : UIButton) {
print("Click")
}
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
func buttonTapped(sender : UIButton) {
// code here
}
Replace with this :
btn.addTarget(self, action: #selector(self.click(sender:)), for: .touchUpInside)
I think something else effect to your selector method try to find in your code because your code also working in my project.

How to add custom UIButton programmatically swift

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.

Resources