I have a custom TabBar, with a raised middle button, above the TabBar. When the user has not completed a daily task, the setupIncompleteMiddleButton() should appear, indicating that. However, once the user completes the task, I would like setupCompleteMiddleButton() to appear, indicating the have completed the task. I don't know how to do this - I shouldn't call viewDidLoad() in the controller and when calling it, it does nothing to refresh the view. Refreshing the TabBar does nothing.
This is my TabBar controller:
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func viewDidLoad() {
super.viewDidLoad()
// This is where I currently decide which button to show the "complete" one if the task is done, and the incomplete one if not.
self.delegate = self
if UserData.hasCompletedDailyTask() == true {
setupCompleteMiddleButton()
} else {
setupIncompleteMiddleButton()
}
}
// Incomplete button
func setupIncompleteMiddleButton() {
let middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemYellow
middleButton.setImage(UIImage(systemName: "sun.max.fill"), for: .normal)
middleButton.imageView?.tintColor = UIColor.white
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Complete button
func setupCompleteMiddleButton() {
let middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemGreen
middleButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
middleButton.imageView?.tintColor = UIColor.white
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
#objc func middleButtonAction(sender: UIButton) {
self.selectedIndex = 1
}
}
Thank you!
You can try this for globally single object of button and just change the image whenever task is complete or not.
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
// TabbarController hode this button
var middleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupButton()
// This is where I currently decide which button to show the "complete" one if the task is done, and the incomplete one if not.
self.delegate = self
// taskCompletion is a call back when UserData finish its task
if UserData.taskCompletion {
setupCompleteMiddleButton()
} else {
setupIncompleteMiddleButton()
}
}
func setupButton() {
middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemYellow
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Incomplete button
func setupIncompleteMiddleButton() {
middleButton.backgroundColor = UIColor.systemYellow
middleButton.setImage(UIImage(systemName: "sun.max.fill"), for: .normal)
}
// Complete button
func setupCompleteMiddleButton() {
// change button color and image
middleButton.backgroundColor = UIColor.systemGreen
middleButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
}
#objc func middleButtonAction(sender: UIButton) {
self.selectedIndex = 1
}
Maybe you can try this:
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
// TabbarController hode this button
var middleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// This is where I currently decide which button to show the "complete" one if the task is done, and the incomplete one if not.
self.delegate = self
// taskCompletion is a call back when UserData finish its task
if UserData.taskCompletion {
setupCompleteMiddleButton()
}
setupIncompleteMiddleButton()
}
// Incomplete button
func setupIncompleteMiddleButton() {
// initialize button
middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemYellow
middleButton.setImage(UIImage(systemName: "sun.max.fill"), for: .normal)
middleButton.imageView?.tintColor = UIColor.white
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Complete button
func setupCompleteMiddleButton() {
// change button color and image
middleButton.backgroundColor = UIColor.systemGreen
middleButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
}
#objc func middleButtonAction(sender: UIButton) {
self.selectedIndex = 1
}
}
Related
I need to change alpha value of the button in another class. But the problem is button created as "lazy var" so I can not change that value.
lazy var middleButton: UIButton = {
let button = UIButton(type: .custom)
button.frame.size = CGSize(width: 56, height: 56)
button.layer.cornerRadius = button.frame.width / 2
button.layer.masksToBounds = true
button.backgroundColor = .white
button.setImage(UIImage(named: "iconBasket"), for: .normal)
button.contentMode = .center
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
button.addTarget(self, action: #selector(btnBasketClicked))
addSubview(button)
return button
}()
I want this button's alpha as 0.2 when view is scrolling. Here is the code
extension ProductListView: UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0{
// alpha = 0.2
MyTabBar.shared.scrollDown()
}
else{
// alpha = 1.0
MyTabBar.shared.scrollUp()
}
}
}
func scrollDown(){
middleButton.alpha = 0.2
}
I've tried lots of way but doesn't work. Calling "addSubView()" function in "layoutSubviews()" solve my problem but this causes another problem which my function "basketButtonClicked()" are not called. I used Delegate pattern and here it is.
protocol BasketButtonDelegate: class {
func basketButtonClicked()
}
#objc private func btnBasketClicked(_ sender: UIButton!){
delegate?.basketButtonClicked()
}
override func layoutSubviews() {
super.layoutSubviews()
addSubview(middleButton)
}
When I call "addSubView" function in "layoutSubviews()", "basketbuttonClicked" never called.
extension TabCoordinator : BasketButtonDelegate
{
func basketButtonClicked(){
log.debug("basketButtonClicked")
let coor = CartCoordinator(navigationController, hidesBar: true)
childCoordinators.append(coor)
coor.start()
}
}
(I assigned delegate so the problem is not about it.)
A bit complicated but I hope we can figure it out.
You need to add protocol to your MyTabBar class . It should be like this
class MyTabBar {
static var shared = MyTabBar()
weak var delegate : abc?
func scrollDown(){
delegate?.xyz()
}
}
protocol abc : AnyObject {
func xyz()
}
And in your class
class btnView : UIView , abc{
lazy var middleButton: UIButton = {
let button = UIButton(type: .custom)
button.frame.size = CGSize(width: 56, height: 56)
button.layer.cornerRadius = button.frame.width / 2
button.layer.masksToBounds = true
button.backgroundColor = .red
button.contentMode = .center
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
button.addTarget(self, action: #selector(btnBasketClicked), for: .touchUpInside)
return button
}()
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
#objc func btnBasketClicked(){
print("Im here")
}
func addMiddleButton(){
self.addSubview(middleButton)
}
func alphaaa(){
self.middleButton.alpha = 0.2
}
func xyz() {
self.alphaaa()
}
}
Last , in your ProductListView create and instance of your view , or if you add with autolayout just call 2. function in your viewDidLoad
var viewwww = btnView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
viewwww.addMiddleButton() // call to add btn to custom view
and extension
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0{
// alpha = 0.2
MyTabBar.shared.delegate = viewwww
MyTabBar.shared.scrollDown()
}
else{
// alpha = 1.0
MyTabBar.shared.scrollUp()
}
}
I have added buttons to horizontal Scroll View in iOS.
override func viewDidLoad() {
super.viewDidLoad()
setUpScrollView()
// Do any additional setup after loading the view, typically from a nib.
}
func setUpScrollView() {
let buttonPadding:CGFloat = 10
var xOffset:CGFloat = 10
for i in 0 ... 10 {
let button = UIButton()
button.tag = i
button.backgroundColor = UIColor.red
button.setTitle("\(i)", for: .normal)
if(button.tag==currentTag){
button.addTarget(self, action: #selector(btnTouchUnselect), for: UIControlEvents.touchUpInside)
}
else{
button.addTarget(self, action: #selector(btnTouch), for: UIControlEvents.touchUpInside)
}
button.frame = CGRect(x: xOffset, y: CGFloat(buttonPadding), width: 70, height: 30)
xOffset = xOffset + CGFloat(buttonPadding) + button.frame.size.width;
scrollView.addSubview(button)
}
scrollView.contentSize = CGSize(width: xOffset, height: scrollView.frame.height)
}
#objc func btnTouch(button:UIButton){
print("tap touch",button.tag)
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 1.0
currentTag = button.tag
}
#objc func btnTouchUnselect(button:UIButton){
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1.0
}
}
I want a button to get a different border color when the user clicks it and the others to stay black. But when I am using this code it turns all clicked button borders black and doesn't turn the clicked one white.
Aim Example:-Suppose I have 10 buttons, I want when button 1's is clicked then its border turns white and others' remain black; if button 2 is clicked then the borders of all turn black again including button 1, only the border of button 2 changes to white.
I need some guidance to achieve this.
I think the problem is that your buttons are only accessible in setScrollView.
so when a button tapped, in #Anton answer, just the clicked button is known in the didTap function.
I think that a better idea is to make an array of UIButtons,
initiate them in setScrollView,
and then use #Anton didTap function
class yourClass {
var buttons : [UIButton] = Array(repeatElement(UIButton(), count: 10))
override func viewDidLoad() {
super.viewDidLoad()
setUpScrollView()
// Do any additional setup after loading the view, typically from a nib.
}
func setUpScrollView() {
let buttonPadding:CGFloat = 10
var xOffset:CGFloat = 10
for i in 0...9 {
buttons[i].tag = i
buttons[i].backgroundColor = UIColor.red
buttons[i].setTitle("\(i)", for: .normal)
//Other functionality that you had set here before...
}
#objc func didTap(clickedButton: UIButton) {
for eachButton in self.buttons {
if eachButton.tag == clickedButton.tag {
eachButton.layer.borderColor = UIColor.white.cgColor
} else {
eachButton.layer.borderColor = UIColor.balck.cgColor
}
}
currentTag = clickedButton.tag
}
}
try this code
var allButtons = [UIButton]()
override func viewDidLoad() {
super.viewDidLoad()
setUpScrollView()
// Do any additional setup after loading the view, typically from a nib.
}
func setUpScrollView() {
let buttonPadding:CGFloat = 10
var xOffset:CGFloat = 10
for i in 0 ... 10 {
let button = UIButton()
button.tag = i
button.backgroundColor = UIColor.red
button.layer.borderWidth = 1.0
button.setTitle("\(i)", for: .normal)
button.addTarget(self, action: #selector(didTap), for: UIControlEvents.touchUpInside)
button.frame = CGRect(x: xOffset, y: CGFloat(buttonPadding), width: 70, height: 30)
xOffset = xOffset + CGFloat(buttonPadding) + button.frame.size.width;
scrollView.addSubview(button)
allButtons.append(button)
}
scrollView.contentSize = CGSize(width: xOffset, height: scrollView.frame.height)
}
#objc func didTap(button: UIButton) {
print("tap touch",button.tag)
allButtons.forEach { inButton in
if inButton == button {
button.layer.borderColor = UIColor.black.cgColor
} else {
button.layer.borderColor = UIColor.white.cgColor
}
}
currentTag = button.tag
}
I have a custom UIToolbar class with two UIBarButtonItem. I know I can create custom delegate action for UIBarButtonItem item. But how can I use closure as UIBarButtonItem action in custom UIToolbar class initialization?
class KeyboardToolBar: UIToolbar
{
let done = UIButton.init()
init() {
super.init(frame: .zero)
self.backgroundColor = UIColor.gray
self.sizeToFit()
let flexBarBtn = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
done.frame = CGRect(x: 0, y: 0, width: 50, height: 44)
done.setTitle("Done", for: .normal)
done.setTitleColor(.black, for: .normal)
let doneBarBtn:UIBarButtonItem! = UIBarButtonItem.init(customView: done)
self.items = [flexBarBtn,doneBarBtn]
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In ViewController
super.viewDidLoad()
txt.keyboardType = .numberPad
txt.inputAccessoryView = KeyboardToolBar()
// How can I use some thing like this
// txt.inputAccessoryView = KeyboardToolBar(doneBtnAction: {
// print("done button pressed")
// })
}
Here you go ...
class KeyboardToolBar: UIToolbar {
let done = UIButton.init()
var doneBtnAction:((Void) -> Void)?
convenience init(_ doneBtnAction: #escaping (Void) -> Void) {
self.init()
self.doneBtnAction = doneBtnAction
}
private init() {
super.init(frame: .zero)
self.backgroundColor = UIColor.gray
self.sizeToFit()
let flexBarBtn = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
done.frame = CGRect(x: 0, y: 0, width: 50, height: 44)
done.setTitle("Done", for: .normal)
done.setTitleColor(.black, for: .normal)
done.addTarget(self, action: #selector(callbackDoneButton(_:)), for: .touchUpInside)
let doneBarBtn:UIBarButtonItem! = UIBarButtonItem.init(customView: done)
self.items = [flexBarBtn,doneBarBtn]
}
func callbackDoneButton(_ id:Any) -> Void {
if self.doneBtnAction != nil {
self.doneBtnAction!()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In ViewController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
txt.keyboardType = .numberPad
txt.inputAccessoryView = KeyboardToolBar.init( {
(Void) -> Void in
print("done");
})
}
Im reading Apples swift (iOS) documentation but its written for Swift 2 and i use Swift 3. I want to add a button programmatically but its seems there is a change and I can't find how to fix it.
Here is the Code for the Swift 2 example:
import UIKit
class RatingControl: UIView {
// MARK: Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Buttons
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.red()
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)
addSubview(button)
}
override func intrinsicContentSize() -> CGSize {
return CGSize(width: 240, height: 44)
}
// MARK: Button Action
func ratingButtonTapped(button: UIButton){
print("Button pressed")
}
}
The only change i made after the 'fix-it' showed the error is this in the selector:
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchDown)
This should have printed "Button pressed" but it doesn't. Any help?
My code:
button.backgroundColor = UIColor.red
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), for: .touchDown)
override var intrinsicContentSize : CGSize {
//override func intrinsicContentSize() -> CGSize {
//...
return CGSize(width: 240, height: 44)
}
// MARK: Button Action
func ratingButtonTapped(_ button: UIButton) {
print("Button pressed đź‘Ť")
}
Try something like this. I haven't tested but it should work:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.red
button.addTarget(self, action: #selector(ratingButtonTapped), for: .touchUpInside)
addSubview(button)
func ratingButtonTapped() {
print("Button pressed")
}
Found the solution. For some reason the:
func ratingButtonTapped(button: UIButton)
needs an "_" before button. So it should be :
func ratingButtonTapped(_ button: UIButton)
And the other part of the code must be :
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), for: .touchDown)
Thanks for helping :) Your method may be correct also but thats the one Apple wants it.
I want to create custom clear button on UITextField, that is to use rightView and put image there, the problem is attaching the original clear button event to that custom rightView.
In Objective-C i can do that this way:
SEL clearButtonSelector = NSSelectorFromString(#"clearButton");
// Reference clearButton getter
IMP clearButtonImplementation = [self methodForSelector:clearButtonSelector];
// Create function pointer that returns UIButton from implementation of method that contains clearButtonSelector
UIButton * (* clearButtonFunctionPointer)(id, SEL) = (void *)clearButtonImplementation;
// Set clearTextFieldButton reference to “clearButton” from clearButtonSelector
UIButton *_clearTextFieldButton = clearButtonFunctionPointer(self, clearButtonSelector);
[_clearTextFieldButton setImage:[UIImage imageNamed:#"icon_remove"] forState:UIControlStateNormal];
self.hasClearButtonAsRightView = YES;
now how to convert this to Swift?
or any ideas to workaround it?
You can add a custom button as right view of the UITextField like this
class CustomTextField : UITextField
{
override init(frame: CGRect) {
super.init(frame: frame)
let clearButton = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 15, height: 15))
clearButton.setImage(UIImage(named: "clear.png")!, forState: UIControlState.Normal)
self.rightView = clearButton
clearButton.addTarget(self, action: "clearClicked:", forControlEvents: .touchUpInside)
self.clearButtonMode = .never
self.rightViewMode = .always
}
func clearClicked(sender: UIButton)
{
self.text = ""
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Implementing a custom text field as suggested in the other answers is not a good idea. You should try to use extensions rather than inheritance if at all possible, because with inheritance you are much more likely to need to make major changes to your codebase in response to changes, whereas using extensions you are much more flexible to change.
I strongly suggest that instead of implementing a custom text field, you extend the UITextField class like this:
extension UITextField {
func applyCustomClearButton() {
clearButtonMode = .Never
rightViewMode = .WhileEditing
let clearButton = UIButton(frame: CGRectMake(0, 0, 16, 16))
clearButton.setImage(UIImage(name: "iCFieldClear")!, forState: .Normal)
clearButton.addTarget(self, action: "clearClicked:", forControlEvents: .TouchUpInside)
rightView = clearButton
}
func clearClicked(sender:UIButton) {
text = ""
}
}
Then to use it you just do this:
yourTextField.applyCustomClearButton()
Here is my solution in Swift 3. In addition to the already existing answer, I also made sure that both left and right views of the textfield (i.e. the search magnifier image view and the custom clear button) have a padding to their left/right by overriding leftViewRect() and rightViewRect(). Otherwise, they will stick right on the edges of the textfield.
class CustomTextField: UITextField
{
fileprivate let searchImageLength: CGFloat = 22
fileprivate let cancelButtonLength: CGFloat = 15
fileprivate let padding: CGFloat = 8
override init( frame: CGRect )
{
super.init( frame: frame )
self.customLayout()
}
required init?( coder aDecoder: NSCoder )
{
super.init( coder: aDecoder )
self.customLayout()
}
override func leftViewRect( forBounds bounds: CGRect ) -> CGRect
{
let x = self.padding
let y = ( bounds.size.height - self.searchImageLength ) / 2
let rightBounds = CGRect( x: x, y: y, width: self.searchImageLength, height: self.searchImageLength )
return rightBounds
}
override func rightViewRect( forBounds bounds: CGRect ) -> CGRect
{
let x = bounds.size.width - self.cancelButtonLength - self.padding
let y = ( bounds.size.height - self.cancelButtonLength ) / 2
let rightBounds = CGRect( x: x, y: y, width: self.cancelButtonLength, height: self.cancelButtonLength )
return rightBounds
}
fileprivate func customLayout()
{
// Add search icon on left side
let searchImageView = UIImageView()
searchImageView.contentMode = .scaleAspectFit
let searchIcon = UIImage( named: "search_magnifier" )
searchImageView.image = searchIcon
self.leftView = searchImageView
self.leftViewMode = .always
// Set custom clear button on right side
let clearButton = UIButton()
clearButton.setImage( UIImage( named: "search_cancel" ), for: .normal )
clearButton.contentMode = .scaleAspectFit
clearButton.addTarget( self, action: #selector( self.clearClicked ), for: .touchUpInside )
self.rightView = clearButton
self.clearButtonMode = .never
self.rightViewMode = .whileEditing
}
#objc fileprivate func clearClicked( sender: UIButton )
{
self.text = ""
}
}
with iOS 14, none of the solution were working for me. the clear button was getting wrong offset for different device sizes.
I had the image. if you dont have it, you can download it from SF Symbols. the name is xmark.circle.fill
In the end, I used this
let customClearButton = UIButton.appearance(whenContainedInInstancesOf: [UITextField.self])
customClearButton.setImage(UIImage(named: "icon-x"), for: .normal)
Updated to Swift 5, based on #marmoy answer:
public func addClearAllCustomButton() {
clearButtonMode = .never
rightViewMode = .whileEditing
let clearButton = UIButton(frame: rightViewRect(forBounds: bounds))
clearButton.setImage(UIImage(named: "clearAll"), for: .normal)
clearButton.addTarget(self, action: #selector(didTouchClearAllButton(sender:)), for: .touchUpInside)
rightView = clearButton
}
public func removeClearAllButton() {
rightViewMode = .never
}
#objc func didTouchClearAllButton(sender: UIButton) {
text = ""
}
For rigth padding & listen the clear delegate of textfield
class SearchBoxTextField: UITextField {
override open func awakeFromNib() {
super.awakeFromNib()
self.initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func initialize() {
let clearButton = UIButton(frame: CGRect(x: 0, y: 0, width: 12, height: 12))
clearButton.setImage(UIImage(named: "removeIcon")!, for: .normal)
let clearView = UIView(frame: CGRect(x: 0, y: 0, width: 22, height: 12))
clearView.addSubview(clearButton)
self.rightView = clearView
clearButton.addTarget(self, action: #selector(clearClicked), for: .touchUpInside)
self.clearButtonMode = .never
self.rightViewMode = .whileEditing
}
#objc func clearClicked(sender:UIButton) {
self.text = ""
_ = self.delegate?.textFieldShouldClear?(self)
}
}