I have a custom navigation bar subclass:
class ProfileNavigationBar: UINavigationBar {
var titleLabel: UILabel
var backButton: UIBarButtonItem
var friendsButton: FriendsButton?
required init?(coder: NSCoder) {
titleLabel = UILabel(frame: CGRect(x: 0, y: 40, width: 320, height: 40))
backButton = UIBarButtonItem.backButton(nil, action: nil)
friendsButton = FriendsButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
let item = UINavigationItem()
item.titleView = titleLabel
item.leftBarButtonItem = backButton
item.hidesBackButton = true
let friendsItem = UIBarButtonItem(customView: friendsButton!)
item.rightBarButtonItems = [friendsItem]
pushItem(item, animated: false)
}
}
where the FriendsButton resizes itself when it's state property is changed.
Problem is that when the view is first loaded, it appears like this, with the back button and the FriendsButton right at the edge of the nav bar: (.loading state)
However, when I change the FriendsButton state to .add, it appears normally like this:
How can I fix this?
Here is the implementation of FriendsButton:
class FriendsButton: UIView {
var state: FriendsButtonState {
didSet {
style(selected: state)
}
}
var title: String = "" {
didSet {
set(title: title)
}
}
var font = UIFont.boldSystemFont(ofSize: 11)
private var imageView: UIImageView!
private var button: UIButton!
var loading: UIActivityIndicatorView!
init(frame: CGRect, state: FriendsButtonState = .loading) {
self.state = state
super.init(frame: frame)
backgroundColor = .yellow
let plusSize = frame.size.height/2
let plusYValue = (frame.size.height-plusSize)/2
imageView = UIImageView(frame: CGRect(x: plusYValue*2, y: plusYValue, width: plusSize, height: plusSize))
imageView.contentMode = .scaleAspectFit
addSubview(imageView)
let titleSize = (title as NSString).size(attributes: [NSFontAttributeName : font])
button = UIButton(frame: CGRect(x: plusYValue*3.5 + plusSize, y: 0, width: titleSize.width, height: frame.size.height))
addSubview(button)
loading = UIActivityIndicatorView(activityIndicatorStyle: .gray)
loading.center = center
addSubview(loading)
style(selected: state)
updateSize()
}
func addTarget(object: Any, selector: Selector) {
button.addTarget(object, action: selector, for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func style(selected: FriendsButtonState) {
configureBorder(state: selected)
loading.startAnimating()
loading.isHidden = state != .loading
isHidden = false
switch state {
case .friends:
backgroundColor = .black
button.setTitleColor(.white, for: .normal)
imageView.image = #imageLiteral(resourceName: "friends-tick")
title = "Friends"
// ... + all other cases
}
self.updateSize()
}
private func configureBorder(state: FriendsButtonState) {
layer.borderColor = UIColor.black.cgColor
layer.borderWidth = state == .loading ? 0 : 1
layer.cornerRadius = 5
}
private func set(title: String) {
let plusSize = frame.size.height/2
let plusYValue = (frame.size.height-plusSize)/2
let titleSize = (title as NSString).size(attributes: [NSFontAttributeName : font])
button.titleLabel?.font = font
button.setTitle(title, for: .normal)
button.frame = CGRect(x: plusYValue*3.5 + plusSize, y: 0, width: titleSize.width, height: frame.size.height)
self.updateSize()
}
private func updateSize() {
if state == .loading {
frame.size.width = frame.size.width
loading.center = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
loading.startAnimating()
return
}
let plusSize = frame.size.height/2
let plusYValue = (frame.size.height-plusSize)/2
let titleSize = (title as NSString).size(attributes: [NSFontAttributeName : font])
let totalWidth = plusYValue*5.5 + plusSize + titleSize.width
frame.size.width = totalWidth
}
EDIT: I have tried setting the button to the .add state initially but it would still appear at the very right of the nav bar until it was changed to the other state. It seems that the first state of the button always make the nav bar to shift all its children to the edge of the frame until it is updated.
EDIT: I wasn't able to reproduce the problem on another project by copying the relevant code, but this is the specific problem I am having (shown in the image below). The gap between the edge of the navigation bar and the back button is not maintained when first navigating to the view (I managed to get a screenshot midway through the navigation push animation). My question is now, what could be causing this?
You'll have to stop the activity indicator, otherwise it won't hide itself:
private func style(selected: FriendsButtonState) {
configureBorder(state: selected)
if (state == .loading) {
loading.startAnimating()
} else {
loading.stopAnimating()
}
loading.isHidden = state != .loading
Btw: You could also skip loading.isHidden = state != .loading if you configure the UIActivityIndicatorView as to hide itself when its stopped:
init(frame: CGRect, state: FriendsButtonState = .loading) {
// ...
loading = UIActivityIndicatorView(activityIndicatorStyle: .gray)
loading.center = center
loading.hidesWhenStopped = true
addSubview(loading)
// ...
}
To lay out the right navigation bar button correctly, you have to modify FriendsButton.updateSize: When in state .loading - at least for the first time - you also have to update the frame.
private func updateSize() {
if state == .loading {
frame.size.width = frame.size.width
loading.center = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
loading.startAnimating()
// do not return here
}
let plusSize = frame.size.height/2
let plusYValue = (frame.size.height-plusSize)/2
let titleSize = (title as NSString).size(attributes: [NSFontAttributeName : font])
let totalWidth = plusYValue*5.5 + plusSize + titleSize.width
frame.size.width = totalWidth
}
I assume the same for the left button, but unfortunalty the your code does not compile here (get an error in the init? method when calling backButton = UIBarButtonItem.backButton(nil, action: nil), because there is no static member backButton on UIBarButtonItem
Now, it works on my machine:
Just after start of the app:
After state change to .friends
State changed back again to .loading
Everything is well aligned, no changes etc.
If this does not hold true for your project, than there might be other aspects that you didn't publish with your code.
You should update the frames of the subviews in layoutSubviews(), so you should override this method. The layoutSubviews() method is called whenever the frame changes and when the layout is flagged as being 'dirty' (by calling setNeedsLayout() you can achieve this).
It looks like you want to set the FriendsButtonState of the friendsButton in the init. Change:
friendsButton = FriendsButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
to
friendsButton = FriendsButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24), state:.add)
Related
I created a badge over bar button like this in order to show no of items in cart.
Class SSBadgeButton
class SSBadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addBadgeToButon(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addBadgeToButon(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addBadgeToButon(badge: nil)
}
func addBadgeToButon(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addBadgeToButon(badge: nil)
fatalError("init(coder:) has not been implemented")
}
}
Inside View Controller :
func addBadge(itemvalue: String) {
let bagButton = SSBadgeButton()
bagButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
bagButton.tintColor = UIColor.darkGray
bagButton.setImage(UIImage(named: "ShoppingBag")?.withRenderingMode(.alwaysTemplate), for: .normal)
bagButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
bagButton.badge = itemvalue
bagButton.isUserInteractionEnabled = true
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: bagButton)
}
Using badge function :
self.addBadge(itemvalue: data[0]["total_products_in_cart"].stringValue)
But after adding this badge the bar button item becomes untapable. Control not going inside of the button action method. What should be done here in order to make button tapable.
Please check UITapGestureRecognizer on your parent view of the view controller. if exists removed the tap gesture, this make UIBarButtonItems began to respond properly to all selectors.
I was not able to judge whats the issue with the code of SSBadgeButton.
Later I figured out that what I want to achieve (placing UILabel with UIBarButtonItem) can be achieved through storyboard.
Solution is :
U can drag UIView inside ToolBar and can place UiLabel & Button inside of that view.
Referred from this answer
I am a beginner I want to make a top bar button that has badge like the picture above, after searching on the internet, I can make the badge on the button by implementing the SSBadgeButton like the code below
import UIKit
class SSBadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addBadgeToButon(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addBadgeToButon(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addBadgeToButon(badge: nil)
}
func addBadgeToButon(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addBadgeToButon(badge: nil)
fatalError("init(coder:) has not been implemented")
}
}
as we can see the SSBadgeButtonis UIButton, and I need to convert that SSBadgeButton to UIBarButtonItem. the purpose of this is to make the UIBarButtonItem class to be accessible in the Interface builder as the custom class like the picture below
You don't need to convert the UIButton to UIBarButtonItem, you can always create UIBarbuttonItem using UIButton as shown below
let button = UIButton()
button.setTitle("ABCD", for: .normal)
let uiBarButtonItem = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItems = [uiBarButtonItem]
Instead of UIButton you will use your SSBadgeButton thats all
Hope it helps
you can create UIBarButtonItem with custom button
let button = SSBadgeButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30)
let barBtnItem = UIBarButtonItem(customView: button)
I've added a search bar to my navigation.titleView
self.navigationItem.titleView = searchBar
There's also a BackBarButtonItem with title = ""
self.navigationItem.backBarButtonItem?.title = ""
But then there're gap between Back Button and SearchBar, like this:
I Think that the gap appears here because there's space for title of backBarButtonItem (because my title is null "" but the space still there)
So I want to ask how to omit that gap? I want to make my searchBar nearer my backBarIcon
Thank you so much!
EDIT 1:
I try to change searchBar's frame but it's not working
This is my code
//Change searchBar's frame
let titleViewFrame = (searchController.searchBar.frame)
searchController.searchBar.frame = CGRect(x: titleViewFrame.minX - 20.0, y: titleViewFrame.minY, width: titleViewFrame.width + 20.0, height: titleViewFrame.height)
override func viewDidLoad() {
super.viewDidLoad()
let container = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 22))
let searchBar = UISearchBar()
searchBar.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(searchBar)
let leftButtonWidth: CGFloat = 35 // left padding
let rightButtonWidth: CGFloat = 75 // right padding
let width = view.frame.width - leftButtonWidth - rightButtonWidth
let offset = (rightButtonWidth - leftButtonWidth) / 2
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: container.topAnchor),
searchBar.bottomAnchor.constraint(equalTo: container.bottomAnchor),
searchBar.centerXAnchor.constraint(equalTo: container.centerXAnchor, constant: -offset),
searchBar.widthAnchor.constraint(equalToConstant: width)
])
self.navigationItem.titleView = container
}
You can't do that, there is a default space given which we cannot change if we have back button.
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
self.navigationController?.navigationBar.backIndicatorImage = UIImage(named: "back")
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "back")
self.navigationController?.navigationBar.tintColor = UIColor.lightGray
Below is the screenshot
class SearchBarContainerView: UIView {
let searchBar: UISearchBar
init(customSearchBar: UISearchBar) {
searchBar = customSearchBar
super.init(frame: CGRect.zero)
addSubview(searchBar)
}
override convenience init(frame: CGRect) {
self.init(customSearchBar: UISearchBar())
self.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
searchBar.frame = bounds
}
}
class MyViewController: UIViewController {
func setupNavigationBar() {
let searchBar = UISearchBar()
let searchBarContainer = SearchBarContainerView(customSearchBar: searchBar)
searchBarContainer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44)
navigationItem.titleView = searchBarContainer
}
}
Add a subview(ShareView) but nothing happens when touching a button(coverBtn) on it if I shareVC.addsubView(self) in func showShareView ()
But If superView.addSubview(coverBtn) and superView.addSubview(sharePanel) separately, everything will be fine.
import UIKit
class ShareView: UIView {
weak var shareVC: UINavigationController?
// UI
private lazy var coverView: UIView! = {
let coverView = UIView(frame: UIScreen.mainScreen().bounds)
return coverView
}()
// transluscent cover
private lazy var coverBtn: UIButton! = {
let bounds = UIScreen.mainScreen().bounds
let frame = CGRectMake(0, 0, bounds.width, bounds.height)
let coverBtn = UIButton(frame: frame)
print ("UIScreen.mainScreen().bounds = \(UIScreen.mainScreen().bounds)")
coverBtn.alpha = 0.2
coverBtn.backgroundColor = UIColor.blackColor()
coverBtn.addTarget(self, action: #selector(ShareView.pressCoverBtn), forControlEvents: .TouchUpInside)
return coverBtn
}()
let panelHeight: CGFloat = 215
// share panel
private lazy var sharePanel: UIView! = {
// panel size
let bounds = UIScreen.mainScreen().bounds
let h = 215
let frame = CGRectMake(0, bounds.height, bounds.width, self.panelHeight)
let sharePanel: UIView = UIView(frame: frame)
sharePanel.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1.0)
// label
let labelHeight: CGFloat = 30
let labelWidth: CGFloat = 100
let labelY: CGFloat = 20
let labelFrame = CGRectMake(sharePanel.frame.width/2-labelWidth/2, labelY, labelWidth, labelHeight)
let label = UILabel(frame: labelFrame)
label.text = "分享到"
label.textAlignment = .Center
label.backgroundColor = UIColor.whiteColor()
sharePanel.addSubview(label)
// share buttons
let marginW: CGFloat = 80
let btnInv: CGFloat = 20
let btnCnt: CGFloat = 3
let btnsY = label.frame.maxY + 15
let btnA = (sharePanel.frame.width - 2*marginW - (btnCnt-1)*btnInv)/btnCnt
let wcFrame = CGRectMake(marginW, btnsY, btnA, btnA)
let pyqFrame = CGRectMake(wcFrame.maxX+btnInv, btnsY, btnA, btnA)
let wbFrame = CGRectMake(pyqFrame.maxX+btnInv, btnsY, btnA, btnA)
let wcBtn = UIButton(frame: wcFrame)
let pyqBtn = UIButton(frame: pyqFrame)
let wbBtn = UIButton(frame: wbFrame)
wcBtn.setImage(UIImage(named: "share_wx"), forState: .Normal)
pyqBtn.setImage(UIImage(named: "share_pyq"), forState: .Normal)
wbBtn.setImage(UIImage(named: "share_wb"), forState: .Normal)
sharePanel.addSubview(wcBtn)
sharePanel.addSubview(pyqBtn)
sharePanel.addSubview(wbBtn)
// cancel button
let ccW = sharePanel.frame.width/2
let ccH: CGFloat = 50
let ccFrame = CGRectMake(sharePanel.frame.width/2-ccW/2, wcBtn.frame.maxY+10, ccW, ccH)
let cancelBtn: UIButton = UIButton(frame: ccFrame)
cancelBtn.setBackgroundImage(UIImage(named: "kkk"), forState: .Normal)
cancelBtn.setTitle("取消", forState: .Normal)
cancelBtn.setTitleColor(UIColor.blackColor(), forState: .Normal)
cancelBtn.addTarget(self, action: #selector(ShareView.pressCoverBtn), forControlEvents: UIControlEvents.TouchUpInside)
sharePanel.addSubview(cancelBtn)
return sharePanel
}()
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
// override init(){
// super.init()
// }
override init(frame: CGRect) {
super.init(frame: frame)
self.addCustomView(self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addCustomView (superView: UIView) {
superView.addSubview(coverBtn)
superView.addSubview(sharePanel)
}
#objc func pressCoverBtn() {
print("press cover btn\n")
hideShareView()
}
func showShareView () {
self.addCustomView((shareVC?.view)!)
UIView.animateWithDuration(0.4, animations: {
() -> Void in
self.sharePanel.frame = CGRectMake(0, UIScreen.mainScreen().bounds.height - self.panelHeight, self.sharePanel.frame.width, self.panelHeight)
})
}
func hideShareView() {
coverBtn.removeFromSuperview()
UIView.animateWithDuration(0.4, animations: {
() -> Void in
self.sharePanel.frame = CGRectMake(0, UIScreen.mainScreen().bounds.height, self.sharePanel.frame.width, self.panelHeight)
}) { (finish)-> Void in
self.removeFromSuperview()
}
}
}
you can check the 'Debug View Hierarchy' to focus on layers so that you can see if there is a view over the button blocking it
'Debug view Hierarchy' will give you 3d view of every layer
it is situated in bottom bar
Also, you are adding both the views to the superview, try to add 'coverBtn' button to the 'sharePanel' view and then add the view to the super view, this will create button layer above the 'sharePanel' view
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)
}
}