I got a strange UIButton result while understanding concept of UIControlState. Here is my simple code related to UIButton.
import UIKit
class ViewController: UIViewController {
let normalBtn: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 80, y: 200, width: 200, height: 100)
button.setTitle("π", for: .normal)
button.setTitle("π", for: .highlighted)
button.setTitle("π", for: .selected)
button.setTitle("π", for: .focused)
button.titleLabel?.font = UIFont.systemFont(ofSize: 50)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(normalBtn)
normalBtn.addTarget(self, action: #selector(btnSelected), for: .touchUpInside)
}
#objc func btnSelected() {
print("highlight", normalBtn.isHighlighted)
normalBtn.isSelected = !normalBtn.isSelected
}
}
Here is my scenario about this code.
When I touch normalBtn, state of this button changes normal to
selected.
When I touch normalBtn again, its state changes from
selected to normal.
While these transitions, highlighted property also should be changed, when I touch normalBtn.
So my expectation of changing title is
π -> π while touching -> π(normal to selected)
π -> π while touching -> π(selected to normal)
But the result is,
π -> π while touching -> π(normal to selected)
π -> π(selected to normal)
I really don't know why. Any ideas about this question? Thanks.
Try adding selected state combining with highlighted state.
Example:
button.setTitle("π", for: UIControlState.selected.union(.highlighted))
Alternative syntax to the accepted answer:
button.setTitle("π", for: [.selected, .highlighted])
Related
I have been trying to fix an issue that I encountered with a tableviewcontroller.
The sections within the tableviewcontroller are views:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionLabel = UILabel()
sectionLabel.text = Catalog.sharedInstance.allSections[section - 1].name.localized(lang: defaults.string(forKey: "language")!)
sectionLabel.font = UIFont(name: "Centabel Book", size: 25)
sectionLabel.backgroundColor = UIColor.white
sectionLabel.clipsToBounds = true
return sectionLabel
}
If I try to add a button programatically to put it over the tableview
let actionButton = JJFloatingActionButton()
override func viewDidLoad() {
super.viewDidLoad()
// Configuration of the Floating Action Button
actionButton.buttonColor = .red
actionButton.addItem { item in
self.performSegue(withIdentifier: "goToSettings", sender: nil)
}
actionButton.display(inViewController: self)
actionButton.clipsToBounds = true
// This doesn't work. It is to bring the button to the front. Now it is hidden by the sections.
view.bringSubviewToFront(actionButton)
// Checks if the language setting is nil, which means it is the first time you run the application. If then... selects English as default.
if defaults.string(forKey: "language") == nil {
defaults.set("en", forKey: "language")
}
}
... I don't know why but the viewForHeaderInSection hides the button. You can check it in the picture below:
floating button hided by the headersection
I tried to use the method:
view.bringSubviewToFront(actionButton)
and also:
actionbutton.superview?.bringSubviewToFront(actionButton)
But none of this brings the button to the front.
I am using a floating action button from github called JJFloatingActionButton. But I tried to add a simple UIButton and I got the same error. This is the code that also gave me the same error inside viewDidLoad:
let button = UIButton(frame: CGRect(x: 100, y: 1000, width: 100, height: 50))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
Again the same error. You can check the picture:
adding a simple UIButton happens the same problem
May be you can help me with that.
Thanks in advance.
You could try to use a regular UIViewController (not UITableViewController) and add the UITableView by hand.
Like that you would have better control over the view hierarchy.
Using pod 'Player' in an iOS 9.0 app to play a video. I've subclassed Player class to add a UIButton overlay for closing the window.
It appears fine and has highlighting animation when tapped, but closeTapped isn't called when touching up inside.
import UIKit
import Player
class PlayerViewController: Player, PlayerDelegate {
func install() {
view.frame = presentor.view.bounds
presentor.addChildViewController(self)
presentor.view.addSubview(view)
didMove(toParentViewController: presentor)
let closeImage = UIImage(named: "close")!
let closeButton = UIButton(type: .custom)
view.addSubview(closeButton)
closeButton.setImage(closeImage, for: .normal)
closeButton.autoPinEdge(toSuperviewEdge: .top, withInset: 25)
closeButton.autoPinEdge(toSuperviewEdge: .right, withInset: 15)
closeButton.autoSetDimensions(to: CGSize(width: 50, height: 50))
closeButton.addGestureRecognizer(UITapGestureRecognizer())
closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
}
func closeTapped() {
logger.debug("Player close tapped")
}
}
I also tried having closeTapped(sender: Any?), didn't help.
Why isn't closeTapped called?
You don't need to add a TapGestureRecognizer to the button. For swift 3.0 you can do it like this:
button.addTarget(self, action: #selector(closeTapped(sender:)), for: .touchUpInside)
func closeTapped(sender: UIButton) {
}
I think the top of your button u added a UITapGestureRecognizer(). Which itself has a #selector. So Your button default .touchUpInside controller is not calling.
Try with commenting
//closeButton.addGestureRecognizer(UITapGestureRecognizer())
this line.
Let me know is this helpful or not.
I have a scrollview inside a view. Inside the scrollview I create programmatically 5 buttons. Every button loads a different image with a different tag each one. I added a function that is called when pressing the buttons.
let avatarsListScrollingView = avatarsListView(CGSizeMake(70.0, 55.0), avatarCount: 5)
func avatarsListView(buttonSize:CGSize, avatarCount:Int) -> UIView {
**CODE**
for i in 0...(avatarCount-1) {
let button = UIButton(type: .Custom)
**CODE**
button.setImage(UIImage(named: avatarsList[i]), forState: .Normal)
button.tag = i
button.addTarget(self, action: "avatarListSelected:", forControlEvents: .TouchUpInside)
avatarButtonView.addSubview(button)
}
return avatarButtonView
}
Then when pressing the buttons, I call to "avatarListSelected":
func avatarListSelected(sender:UIButton){
if let image = sender.imageView?.image?.imageWithRenderingMode(.AlwaysTemplate) {
sender.setImage(image, forState: .Normal)
sender.tintColor = UIColor.redColor()
}
self.addAvatarView.reloadInputViews()
}
This function tints the image button to red, it is working fine, but the problem is that when I press some other button, I want the other one goes to the original color. Right now every button that I press gets in red.
I tried to add the call to "self.addAvatarView.reloadInputViews()" to try to "redraw" again all the buttons, but never gets called.
Do you guys know some way to do this?
Thanks to everybody!
This is the final code that solved the problem:
func avatarListSelected(sender:UIButton){
print(sender.tag)
if let image = sender.imageView?.image?.imageWithRenderingMode(.AlwaysTemplate) {
sender.setImage(image, forState: .Normal)
sender.tintColor = UIColor.redColor()
}
for view in self.avatarButtonView.subviews as [UIView] {
if let btn = view as? UIButton {
if btn.tag != sender.tag {
btn.setImage(UIImage(named: avatarsList[btn.tag]), forState: .Normal)
}
}
}
}
Create a property selectedButton: UIButton? and keep a reference to the selected button there. Don't forget to update it in avatarListSelected method and before you change it, if it isn't nil, change its color to original (and then change it).
If the buttons have different original colors, subclass UIButton class and keep the original color there.
I don't know if is better approach or answer, but, i maybe could delivery this using this approach:
Create a method that will "fill" the color for your choice button and "clear" color to others , but its a method that loop through UIScrollView and look for each UIButton. Something like this :
func setBackgroundColorButton(color:UIColor , buttonTag:Int){
for view in self.scrollView.subviews as [UIView] {
if let btn = view as? UIButton {
if btn == buttonTag {
btn.tintColor = color
} else {
btn.tintColor = UIColor.whiteColor()
}
}
}
}
This is the concept, i didn't tested, but maybe just need adjust to search inside your scroll view or similar.
But with this will be work nice i believe :D
You could do it like this
for i in 0...5 {
let button = UIButton(type: .Custom)
let x = 50 * i + 10
let y = 50
button.frame = CGRectMake(CGFloat(x), CGFloat(y), 40, 40)
button.setTitle("\(i)", forState: .Normal)
button.tag = i
button.backgroundColor = UIColor.greenColor()
button.tintColor = UIColor.blueColor()
button.addTarget(self, action: "avatarListSelected:", forControlEvents: .TouchUpInside)
self.view.addSubview(button)
}
func avatarListSelected(sender : UIButton){
sender.backgroundColor = UIColor.orangeColor()
for view in self.view.subviews{
if(view.isKindOfClass(UIButton)){
let button = view as! UIButton
if button.tag != sender.tag{
button.backgroundColor = UIColor.greenColor()
}
}
}
}
The frame etc is just for demonstation purpose only, you should of course use your own value. The tintColor property is not valid for all button types. Read the documentation for more information.
This seems to have been asked a few times in swift and objc, but I can't see a correct answer for swift so hopefully someone can help me this time. I have created a custom accessory view button, but need the correct button action: as "accessoryButtonTapped" is an unrecognised selector.
What is the selector needed to call the tableViewCellDelegate method willSelectRowAtIndexPath?
The code I have is:
let cellAudioButton = UIButton(type: .Custom)
cellAudioButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
cellAudioButton.addTarget(self, action: "accessoryButtonTapped", forControlEvents: .TouchUpInside) //INCORRECT ACTION:
cellAudioButton.setImage(UIImage(named: "blueSpeaker.png"), forState: .Normal)
cellAudioButton.contentMode = .ScaleAspectFit
cell.accessoryView = cellAudioButton as UIView
Thanks in advance to anyone who can help.
Why do you need to call willSelectRowAtIndexPath? You have done everything right and to solve your unrecognized selector error just make a function that will be called when you tap on the cell.accessoryView. In your case:
func accessoryButtonTapped(){
print("Tapped")
}
Update
If you want to get the indexPath you could just
Add a tag to your cellAudioButton:
cellAudioButton.tag = indexPath.row
In your addTarget add a : to pass a parameter
And in your function
func accessoryButtonTapped(sender : AnyObject){
print(sender.tag)
print("Tapped")
}
So the whole code:
let cellAudioButton = UIButton(type: .Custom)
cellAudioButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
cellAudioButton.addTarget(self, action: "accessoryButtonTapped:", forControlEvents: .TouchUpInside)
cellAudioButton.setImage(UIImage(named: "blueSpeaker.png"), forState: .Normal)
cellAudioButton.contentMode = .ScaleAspectFit
cellAudioButton.tag = indexPath.row
cell.accessoryView = cellAudioButton as UIView
func accessoryButtonTapped(sender : AnyObject){
print(sender.tag)
print("Tapped")
}
Swift 3.1 Updated Solution of Rashwan
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//add button as accessory view
let cellAudioButton = UIButton(type: .custom)
cellAudioButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
cellAudioButton.addTarget(self, action: #selector(ViewController.accessoryButtonTapped(sender:)), for: .touchUpInside)
cellAudioButton.setImage(UIImage(named: "closeRed"), for: .normal)
cellAudioButton.contentMode = .scaleAspectFit
cellAudioButton.tag = indexPath.row
cell.accessoryView = cellAudioButton as UIView
return cell
}
func accessoryButtonTapped(sender : UIButton){
print(sender.tag)
print("Tapped")
}
Note:
Here ViewController is the name of Class like LoginViewController
Why don't you use the UITableViewDelegate?
#available(iOS 2.0, *)
optional func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
To clarify that, yet it is possible. You can have custom UI elements act as accessory button.
Here is an example β assuming you're using Interface Builder. Add a UISwitch to the custom cell. Now right-click drag-n-drop from the UITableViewCell to the UISwitch. Select accessoryView as the Outlet connection. Done.
Every time you press the switch button the delegate method will be fired. This should also work for other action elements, e.g., UIButtons. And that is exactly the use case OP asked for.
When I first run my app, I retrieve a number from my server and display it for my UIButton label. Think of this as a notification number displayed on a red UIButton.
When I remove a notification within the app, I want my UIButton label decrement by 1. I am able to get the decremented number from the server after I delete a notification, but I can't display this new number on the UIButton. The button always displays the number when the app is first fired.
I call makeButtonView() method after I remove a notification to update the UIButton
func makeButtonView(){
var button = makeButton()
view.addSubView(button)
button.tag = 2
if (view.viewWithTag(2) != nil) {
view.viewWithTag(2)?.removeFromSuperview()
var updatedButton = makeButton()
view.addSubview(updatedButton)
}else{
println("No button found with tag 2")
}
}
func makeButton() -> UIButton{
let button = UIButton(frame: CGRectMake(50, 5, 60, 40))
button.setBackgroundImage(UIImage(named: "redBubbleButton"), forState: .Normal)
API.getNotificationCount(userID) {
data, error in
button.setTitle("\(data)", forState: UIControlState.Normal)
}
button.addTarget(self, action: "targetController:", forControlEvents: UIControlEvents.TouchUpInside)
return button
}
Use this code for Swift 4 or 5
button.setTitle("Click Me", for: .normal)
I need more information to give you a proper code. But this approach should work:
lazy var button : UIButton = {
let button = UIButton(frame: CGRectMake(50, 5, 60, 40))
button.setBackgroundImage(UIImage(named: "redBubbleButton"), forState: .Normal)
button.addTarget(self, action: "targetController:", forControlEvents: UIControlEvents.TouchUpInside)
return button
}()
func makeButtonView(){
// This should be called just once!!
// Likely you should call this method from viewDidLoad()
self.view.addSubview(button)
}
func updateButton(){
API.getNotificationCount(userID) {
data, error in
// be sure this is call in the main thread!!
button.setTitle("\(data)", forState: UIControlState.Normal)
}
}
There have been some updates since Swift 4. This works for me:
self.button.setTitle("Button Title", for: UIControl.State.init(rawValue: 0))
Replace button with your IBOutlet name. You can also use a variable or array in place of the quoted text.
It's fairly simple ...
import UIKit
class ViewController: UIViewController {
#IBOutlet var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.setTitle("hello world", forState: UIControlState.Normal)
}
}
I believe if you set the state to normal, the value will propagate by default to other states so long as you haven't explicitly set a title for those states.
Said differently, if you set it for normal, it should also display this title when the button enters additional states
UIControlState.allZeros
UIControlState.Application
UIControlState.Disabled
UIControlState.Highlighted
UIControlState.Reserved
UIControlState.Selected
Lastly, here's Apple's documentation in case you have other questions.
Since your API call should be running on a background thread you need to dispatch your UI update back to the main thread like this:
DispatchQueue.main.async {
button.setTitle(βnew valueβ, forState: .normal)
}
After setting the title, just a simple redraw of the button will do:
button.setNeedsDisplay();