Button with right image aligned x points from the right - ios

I'm trying to create a button that looks like this:
So it has a border and a right imageview.
I subclassed UIButton like this:
class ButtonWithRightImage: UIButton {
override func imageRectForContentRect(contentRect:CGRect) -> CGRect {
var imageFrame = super.imageRectForContentRect(contentRect)
imageFrame.origin.x = CGRectGetMaxX(super.titleRectForContentRect(contentRect)) - CGRectGetWidth(imageFrame)
return imageFrame
}
override func titleRectForContentRect(contentRect:CGRect) -> CGRect {
var titleFrame = super.titleRectForContentRect(contentRect)
if (self.currentImage != nil) {
titleFrame.origin.x = CGRectGetMinX(super.imageRectForContentRect(contentRect))
}
return titleFrame
}
}
And I created my button as follows:
lazy var testButton: ButtonWithRightImage = {
let button = ButtonWithRightImage(forAutoLayout: ())
button.setTitle("Privacy", forState: .Normal)
button.setTitleColor(UIColor.DarkBlue(), forState: .Normal)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.grey().CGColor
button.setImage(UIImage(named: "arrow"), forState: .Normal)
button.contentHorizontalAlignment = .Left
button.contentEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0)
return button
}()
Now my button looks like this:
As you can see the image is not aligned with 20 points from the right.
I can't figure out how to do this. I'm using autolayout to display the button so I can't just modify the frames I guess.

As your ButtonWithRightImage class need modification to achieve output as per your requirement.
class ButtonWithRightImage: UIButton {
override func imageRectForContentRect(contentRect:CGRect) -> CGRect {
var imageFrame = super.imageRectForContentRect(contentRect)
imageFrame.origin.x = CGRectGetWidth(contentRect) - CGRectGetWidth(imageFrame)
return imageFrame
}
override func titleRectForContentRect(contentRect:CGRect) -> CGRect {
var titleFrame = super.titleRectForContentRect(contentRect)
if (self.currentImage != nil) {
titleFrame.origin.x = CGRectGetMinX(super.imageRectForContentRect(contentRect))
}
return titleFrame
}
}
Issue is you need to minus image width from container width. Instead of container maxX value.
Let me know if you have any comment on the same.
Thanks

Set imageEdgeInsets for button Like Below code:-
lazy var testButton: ButtonWithRightImage =
{
let button = ButtonWithRightImage(forAutoLayout: ())
button.setTitle("Privacy", forState: .Normal)
button.setTitleColor(UIColor.DarkBlue(), forState: .Normal)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.grey().CGColor
button.setImage(UIImage(named: "arrow"), forState: .Normal)
button.contentHorizontalAlignment = .Left
button.imageEdgeInsets = UIEdgeInsetsMake(0, 280, 0, 0)
return button
}()

Swift 3 and above solution based on MinuMaster's answer.
Furthermore, provision is added to set right padding for image and left padding for text from storyboard.
Also, have handled the text length so it should not overlap image.
class ButtonWithRightImage: UIButton {
#IBInspectable var imagePaddingRight: CGFloat = 0.0
#IBInspectable var textPaddingLeft: CGFloat = 0.0
override func imageRect(forContentRect contentRect:CGRect) -> CGRect {
var imageFrame = super.imageRect(forContentRect: contentRect)
imageFrame.origin.x = contentRect.width - imageFrame.width - imagePaddingRight
return imageFrame
}
override func titleRect(forContentRect contentRect:CGRect) -> CGRect {
var titleFrame = super.titleRect(forContentRect: contentRect)
if (self.currentImage != nil) {
titleFrame.origin.x = super.imageRect(forContentRect: contentRect).minX + textPaddingLeft
if titleFrame.size.width > frame.size.width - (imageView?.frame.size.width)! - imagePaddingRight {
titleFrame.size.width = titleFrame.size.width - (imageView?.frame.size.width)! - imagePaddingRight
}
}
return titleFrame
}
}

Related

Reposition other UIButton after delete one

I have a code where the user can input several ingredients and can also delete them.
Method: input ingredient in searchBar then create UIButton for that input and add into UIView. I also have a function to arrange position of all buttons properly
class SearchViewController: UIViewController {
var listInputView = UIView()
let spacingX: CGFloat = 10
let spacingY: CGFloat = 10
let btnHeight: CGFloat = 30
var heightConstraint: NSLayoutConstraint = NSLayoutConstraint()
var listBtn = [UIButton]()
//and also initialization of other components like UISearchBar,...
override func viewDidLoad() {
super.viewDidLoad()
setupSegmentControl()
setupView()
listInputView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint = listInputView.heightAnchor.constraint(equalToConstant: 10)
}
func setupSegmentControl() {
//setup UISegmentedControl
}
fileprivate func setupView() {
//setup UISearchBar
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if segmentControl.selectedSegmentIndex == 0 {
//search from input menu
}
else {
//search from input ingredients
inputSearchBar.append(mainSearchBar.text!)
listIngredients()
}
}
func listIngredients() {
view.addSubview(listInputView)
listInputView.isHidden = false
NSLayoutConstraint.activate([
listInputView.topAnchor.constraint(equalTo: mainSearchBar.bottomAnchor, constant: 10),
listInputView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
listInputView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
heightConstraint
])
createIngredientBtn(ingredient: mainSearchBar.text!)
}
// create a button
func createIngredientBtn(ingredient: String) {
let deleteIcon = UIImage(systemName: "multiply")
let btn = UIButton(type: UIButton.ButtonType.custom) as UIButton
btn.setTitle(ingredient, for: UIControl.State.normal)
btn.setTitleColor(UIColor(hexString: "#707070"),for: UIControl.State.normal)
btn.layer.borderColor = UIColor(hexString: "#707070").cgColor
btn.layer.borderWidth = 1.0
btn.frame.size.height = btnHeight
btn.layer.masksToBounds = true
btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
btn.setImage(deleteIcon, for: .normal)
btn.imageView?.contentMode = .scaleAspectFit
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 3)
btn.semanticContentAttribute = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
btn.addTarget(self, action: #selector(deleteSelectedInput), for: UIControl.Event.touchDown)
btn.translatesAutoresizingMaskIntoConstraints = false
listBtn.append(btn)
listInputView.addSubview(btn)
}
// delete a button
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
showListBtn() //I try to call this here to reposition other buttons
viewDidLayoutSubviews()
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showListBtn()
}
//Arrange position of each button and update height of inputView according to them
func showListBtn() {
let listInputViewWidth = listInputView.frame.size.width
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
listBtn.forEach { btn in
// break to newline
if currentOriginX + btn.frame.width > listInputViewWidth {
currentOriginX = 0
currentOriginY += btnHeight + spacingY
}
// set the btn frame origin
btn.frame.origin.x = currentOriginX
btn.frame.origin.y = currentOriginY
// increment current X
currentOriginX += btn.frame.width + spacingX
}
// update listInputView height
heightConstraint.constant = currentOriginY + btnHeight
}
}
Problem: Suppose I have > 1 input ingredients (> 1 button) and I delete one of it, the rest will be overlapped on each other at the leftmost origin (where 1st button is placed). At this moment, the position of them is actually correct, they just don't re-display as they should be.
However, when I try to input new ingredient, they will then display properly.
Here is the image to clarify this better:
Input 3 ingredients: Ing1, Ing2, Ing3
Delete Ing1. Then Ing2 and Ing3 are overlapped at the position of Ing1.
Add new input Ing4. They are all position correctly again.
Why the buttons are not re-position correctly when I delete one of them and how can I solve this ? Please kindly help me. Thank you.
EDIT: I'm able to call viewDidLayoutSubviews after delete but the buttons still not re-draw at new position although the frame.origin has changed correctly.
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
listInputView.removeAllSubviews()
listBtn.forEach { btn in
listInputView.addSubview(btn)
}
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}

Right margin of custom clear button in iOS

I create a custom clear button on my text field with a background image set.
I position it on the right view but it always positions with a default margin. What I want is to have a right margin, for example, of 8.
extension UITextField {
func clearButtonWithImage(_ image: UIImage) {
let clearButton = UIButton()
clearButton.setBackgroundImage(image, for: .normal)
clearButton.alpha = 0.25
clearButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
clearButton.contentMode = .scaleAspectFit
clearButton.addTarget(self, action: #selector(self.clear(sender:)), for: .touchUpInside)
self.rightViewMode = .always
self.clearButtonMode = .never
self.rightView = clearButton
}
#objc func clear(sender: AnyObject) {
self.text = ""
}
}
I use a custom textfield class for changing the style of my textfield on selecting.
My custom Textfield Class:
class customSearchTextField: customTextField {
override var isSelected: Bool {
didSet {
setSelected(isSelected)
}
}
private func setSelected(_ selected: Bool) {
//UIView.animate(withDuration: 0.15, animations: {
self.transform = selected ? CGAffineTransform(scaleX: 1.05, y: 1.05) : CGAffineTransform.identity
self.cornerRadius = selected ? 2.0 : 0.0
if selected {
self.clearButtonWithImage(UIImage())
} else {
self.clearButtonWithImage(#imageLiteral(resourceName: "icClear"))
}
self.layer.shadowColor = UIColor .customDark.cgColor
self.layer.shadowOpacity = selected ? 0.19 : 0.0
//})
}
}
Tried positioning the image frame but it always stays the same.
You should take a look at rightViewRect(forBounds:) method. Create a subclass of UITextField and custom position and size of clear button inside this method.
For example
class customSearchTextField: customTextField {
override var isSelected: Bool {
didSet {
setSelected(isSelected)
}
}
private func setSelected(_ selected: Bool) {
//UIView.animate(withDuration: 0.15, animations: {
self.transform = selected ? CGAffineTransform(scaleX: 1.05, y: 1.05) : CGAffineTransform.identity
self.cornerRadius = selected ? 2.0 : 0.0
if selected {
self.clearButtonWithImage(UIImage())
} else {
self.clearButtonWithImage(#imageLiteral(resourceName: "icClear"))
}
self.layer.shadowColor = UIColor .customDark.cgColor
self.layer.shadowOpacity = selected ? 0.19 : 0.0
//})
}
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.rightViewRect(forBounds: bounds)
rect.origin.x -= 30 // Assume your right margin is 30
return rect
}
}

Paging UIScrollView seems to place content off centre

I have a UIScrollView that I want to have paging functionality (think an initial splash screen). I want that content (a UILabel and a UIImageView) to be placed centrally in each paging view on the scrollView. My problem is is that it is always slightly off centre ().
Here is the complete code:
var splashScreenObjects = [SplashScreenObject]()
var imageViewArray = [UIImageView]()
var subtitleViewArray = [UILabel]()
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
createSplashScreenObjects()
configurePageControl()
configureScrollView()
}
func createSplashScreenObjects() {
let firstScreen: SplashScreenObject = SplashScreenObject(subtitle: "Medication reminders on your phone. Never miss your next dose", image: UIImage(named: "splashScreen1")!)
let secondScreen: SplashScreenObject = SplashScreenObject(subtitle: "Track how good you have been with your medication", image: UIImage(named: "splashScreen2")!)
let thirdScreen: SplashScreenObject = SplashScreenObject(subtitle: "The better you are with your medication, the more points you'll earn!", image: UIImage(named: "splashScreen3")!)
splashScreenObjects.append(firstScreen)
splashScreenObjects.append(secondScreen)
splashScreenObjects.append(thirdScreen)
}
func configureScrollView() {
self.scrollView.layoutIfNeeded()
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.pagingEnabled = true
self.scrollView.delegate = self
let width = view.frame.size.width
for index in 0..<splashScreenObjects.count {
let subtitle = UILabel(frame: CGRectMake((width * CGFloat(index)) + 25, self.scrollView.frame.size.height-75, width-50, 75))
subtitle.text = splashScreenObjects[index].subtitle
subtitle.textAlignment = NSTextAlignment.Center
subtitle.textColor = UIColor.whiteColor()
subtitle.font = UIFont(name:"Ubuntu", size: 16)
subtitle.numberOfLines = 2
subtitle.backgroundColor = UIColor.clearColor()
self.scrollView.addSubview(subtitle)
self.subtitleViewArray.append(subtitle)
subtitle.alpha = 0
let mainImage = UIImageView(frame: CGRectMake((width * CGFloat(index)), 50, width, self.scrollView.frame.size.height-150))
mainImage.image = splashScreenObjects[index].image
mainImage.contentMode = UIViewContentMode.ScaleAspectFit
self.scrollView.addSubview(mainImage)
self.imageViewArray.append(mainImage)
mainImage.alpha = 0
}
self.scrollView.contentSize = CGSizeMake(width * CGFloat(splashScreenObjects.count), self.scrollView.frame.size.height-50)
animateViews(Int(0))
}
func configurePageControl() {
self.pageControl.numberOfPages = splashScreenObjects.count
self.pageControl.currentPage = 0
self.view.addSubview(pageControl)
pageControl.addTarget(self, action: #selector(SplashViewController.changePage(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * self.view.frame.size.width
scrollView.setContentOffset(CGPointMake(x, 0), animated: true)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / self.view.frame.size.width)
pageControl.currentPage = Int(pageNumber)
animateViews(Int(pageNumber))
}
func animateViews(pageNumber: Int) {
UIView.animateWithDuration(0.5, animations: {
self.imageViewArray[pageNumber].alpha = 1.0
self.subtitleViewArray[pageNumber].alpha = 1.0
})
}
Here are my auto layout constraints for the UIScrollView:
Your leading and trailing spaces are both -20, which means that the scroll view is 40 points wider than its superview. Change these to 0.
You should replace
self.scrollView.layoutIfNeeded()
to
self.view.layoutIfNeeded()
because layoutIfNeeded layout caller subviews, not itself. So, scrollView, when you add subtitle and mainImage on it, has wrong frame.

How can I make empty circle on baseball out count?

I am making baseball outcount code based on FoodTracker source code. Here's my code but empty circle doesn't count in this code. How can I make the empty circle rating? I would appreciate if you guys can help me out. Thank you!!
///////
import UIKit
class RatingControl: UIView {
// MARK: Properties
var rating = 0 {
didSet {
setNeedsLayout()
}
}
var ratingButtons = [UIButton]()
var spacing = 3
var circles = 3
// MARK: Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let filledCircleImage = UIImage(named: "checkedRed")
let emptyCircleImage = UIImage(named: "UncheckedRed")
for _ in 0..<3 {
let button = UIButton()
button.setImage(emptyCircleImage, forState: .Normal)
button.setImage(filledCircleImage, forState: .Selected)
button.setImage(filledCircleImage, forState: [.Highlighted, .Selected])
button.adjustsImageWhenHighlighted = false
button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)
ratingButtons += [button]
addSubview(button)
}
}
override func layoutSubviews() {
// Set the button's width and height to a square the size of the frame's height.
let buttonSize = Int(frame.size.height)
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
// Offset each button's origin by the length of the button plus spacing.
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
button.frame = buttonFrame
}
updateButtonSelectionStates()
}
override func intrinsicContentSize() -> CGSize {
let buttonSize = Int(frame.size.height)
let width = (buttonSize + spacing) * circles
return CGSize(width: width, height: buttonSize)
}
// MARK: Button Action
func ratingButtonTapped(button: UIButton) {
rating = ratingButtons.indexOf(button)! + 1
updateButtonSelectionStates()
}
func updateButtonSelectionStates() {
for (index, button) in ratingButtons.enumerate() {
// If the index of a button is less than the rating, that button should be selected.
button.selected = index < rating
}
}
}
if you want zero to count then return 0 as a possible button action. Your rating is already set to zero so technically it does count. If you accidentally tap the first circle and want to undo it you can try something like this in ratingButtonTapped
//rating = ratingButtons.index(of: button)! + 1
rating = (rating == 1) && (ratingButtons.index(of: button) == 0) ? 0 : (ratingButtons.index(of: button)! + 1)

UIView changing its position in swift

How do I make a UIView slide up with a touch of a button from its original position and them bring it back down with a touch of a button? Using Swift and Xcode 6.
I have currently tried this:
#IBOutlet weak var DynView: UIView!
#IBAction func btnUp(sender: AnyObject) {
}
You have to implement an animation changing the DynView position on click. Here's an example:
#IBAction func btnUp(sender: AnyObject) {
let xPosition = DynView.frame.origin.x
let yPosition = DynView.frame.origin.y - 20 // Slide Up - 20px
let width = DynView.frame.size.width
let height = DynView.frame.size.height
UIView.animateWithDuration(1.0, animations: {
dynView.frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
})
}
Hi create this extends if you want. For Swift
Create File Extends.Swift and add this code
/**
Extension UIView
by DaRk-_-D0G
*/
extension UIView {
/**
Set x Position
:param: x CGFloat
by DaRk-_-D0G
*/
func setX(#x:CGFloat) {
var frame:CGRect = self.frame
frame.origin.x = x
self.frame = frame
}
/**
Set y Position
:param: y CGFloat
by DaRk-_-D0G
*/
func setY(#y:CGFloat) {
var frame:CGRect = self.frame
frame.origin.y = y
self.frame = frame
}
/**
Set Width
:param: width CGFloat
by DaRk-_-D0G
*/
func setWidth(#width:CGFloat) {
var frame:CGRect = self.frame
frame.size.width = width
self.frame = frame
}
/**
Set Height
:param: height CGFloat
by DaRk-_-D0G
*/
func setHeight(#height:CGFloat) {
var frame:CGRect = self.frame
frame.size.height = height
self.frame = frame
}
}
For Use (inherits Of UIView)
inheritsOfUIView.setX(x: 100)
button.setX(x: 100)
view.setY(y: 100)
I kinda combined the two most voted answers into one and updated to Swift 3. So basically created an extension that animates a view moving to a different position:
extension UIView {
func slideX(x:CGFloat) {
let yPosition = self.frame.origin.y
let height = self.frame.height
let width = self.frame.width
UIView.animate(withDuration: 1.0, animations: {
self.frame = CGRect(x: x, y: yPosition, width: width, height: height)
})
}
}
// MARK: - Properties
var bottomViewHeight: CGFloat = 200
var isViewHide = false
private let bottomView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let showHideButton: UIButton = {
let button = UIButton()
button.setTitle("Show / Hide", for: .normal)
button.setTitleColor(.black, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(showHideButtonTapped(_:)), for: .touchUpInside)
return button
}()
// MARK: - Lifecycle
override func loadView() {
super.loadView()
view.addSubview(bottomView)
NSLayoutConstraint.activate([
bottomView.heightAnchor.constraint(equalToConstant: bottomViewHeight),
bottomView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
bottomView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
bottomView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
view.addSubview(showHideButton)
NSLayoutConstraint.activate([
showHideButton.widthAnchor.constraint(equalToConstant: 200),
showHideButton.heightAnchor.constraint(equalToConstant: 50),
showHideButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
showHideButton.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
])
}
override func viewDidLoad() {
super.viewDidLoad()
showHideView(isShow: isViewHide)
}
// MARK: - Selectors
#objc func showHideButtonTapped(_ sender: UIButton) {
print("👆 HIDE / SHOW BUTTON")
showHideView(isShow: isViewHide)
}
// MARK: - Functions
private func showHideView(isShow: Bool) {
if isShow {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: self.bottomViewHeight)
}
} else {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
isViewHide = !isViewHide
}

Resources