passing Events from UIButton to TabBarItem - ios

i wanted to make my UITabBarItem's size Bigger then other buttons so i tried this in Subclass of TabBarController :
var button = UIButton(type: .Custom)
button.frame = CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height)
button.setBackgroundImage(buttonImage, forState: .Normal)
button.setBackgroundImage(highlightImage, forState: .Highlighted)
var heightDifference: CGFloat = buttonImage.size.height - self.tabBar.frame.size.height
if heightDifference < 0 {
button.center = self.tabBar.center
}
else {
var center = self.tabBar.center
center.y = center.y - heightDifference / 2.0
button.center = center
}
self.view.addSubview(button)
it worked fine cause now i have a Button on top of my BarButtonItem (expected Behavior ) but now this new button is blocking the TouchEvents which supposed to handle by barbuttonitem , any idea how can i solve this problem ?? i followed this article for getting this newButton:
what i want is look like this :
SOLVED:
All i needed was to disable the user interaction for my button.

you can try below code for your UIButton Clicked event e.g.
-(void)clickedEvent:(id)sender{
.... some code
[self.tabBarController setSelectedIndex:2];
..... some code
}
Hopefully, It should work.

Related

Swift Center Circle Button in TabbarViewController iOS

My scenario, I am trying to create centre circle button in UITabbarViewController. I tried many sample but not getting output like below Image. Please provide some code to achieve this. I am using storyboard for this.
sample Image
You need to do is your UITabbarViewController must contain five tabs in bottom tab bar that you can do by adding five view controller and make sure the middle tab contain no title and icon.
After that in onCreate method of your UITabbarViewController add this custom button Code -
let button = UIButton(type: .custom)
var toMakeButtonUp = 40
button.frame = CGRect(x: 0.0, y: 0.0, width: 65, height: 65)
button.setBackgroundImage(ADD, for: .normal)
button.setBackgroundImage(ADD, for: .highlighted)
let heightDifference: CGFloat = CGFloat(toMakeButtonUp)
if heightDifference < 0 {
button.center = tabBar.center
} else {
var center: CGPoint = tabBar.center
center.y = center.y - heightDifference / 2.0
button.center = center
}
button.addTarget(self, action: #selector(btnTouched), for:.touchUpInside)
view.addSubview(button)
FYI - toMakeButton is the margin from bottom you can set it accordingly.
and #selector(btnTouched) is the function defined in class which will perform on click on the button.

Is it possible to pass data between the UIViewController and UIView using swift?

Is it possible to pass data between the UIViewController and the UIView?
if yes, how?
For example when you press button1 the UIViewController says to the UIView that the person has pressed button1 and then the UIView draws a triangle. But if you then press button2 the UIViewController says to the UIView that he has now pressed button2 and then the UIView eliminates the triangle and draws a circle.
The buttons are build programmatically.
The question is how i can invoke the shapes which i have drawn with drawRect within the UIView in the UIViewController.
class ViewController: UIViewController {
#IBOutlet var UnitViewTabeller: UnitView!
var btn: UIButton!
override viewDidLoad {
self.btn = UIButton(frame: CGRectMake(0.04 * view.bounds.width, 0.91 * view.bounds.height, 0.44 * view.bounds.width, 0.07 * view.bounds.height)) //set frame
self.btn.layer.cornerRadius = 10
self.btn.setTitle("0", forState: .Normal) //set button title
self.btn.setTitleColor(UIColor.whiteColor(), forState: .Normal) //set button title color
self.btn.backgroundColor = UIColor(red: 0.2745, green: 0.2784, blue: 0.2706, alpha: 1.0) //set button background color
self.btn.tag = 0 // set button tag
self.btn.addTarget(self, action: "btnclicked:", forControlEvents: .TouchUpInside) //add button action
self.view.addSubview(btn) //add button in view
self.btnArray.append(btw)
}
func btnclicked(sender: UIButton) {
//draw the circle from the UIView
}
}
class UnitView: UIView {
override func drawRect(rect: CGRect) {
let cirkel = UIGraphicsGetCurrentContext()
let rectangle = CGRect(x: 0.2 * bounds.width, y: 0.15 * bounds.height, width: 0.6 * bounds.width, height: 0.6 * bounds.width)
CGContextSetLineWidth(cirkel, 4.0)
CGContextAddEllipseInRect(cirkel, rectangle)
CGContextStrokePath(cirkel)
}
}
Welcome to SO.
This is a pretty basic beginner's question.
Sure it's possible. You should probably create a custom subclass of UIView. Let's call it ShapeView. Give it methods to draw various shapes, and give it properties to store it's current state.
Now you can add a UIView to your view controller, and go into the "identity inspector" for your view controller scene and change the type of that view to your new ShapeView class. Add an outlet to your shape view in your view controller.
Since your shape view is a custom subclass of UIView with custom methods and properties, your view controller can invoke those methods and set those properties as needed.
UIViewController has a view property of type UIView.
You can access from inside your UIViewController class using self.view
Accessing it from inside your class:
class MyViewController: UIViewController {
var buttonLabel:String
var buttonRadius:CGFloat = 90
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
init(buttonColour:CGColorRef, buttonLabel:String){
self.buttonColour = buttonColour
self.buttonLabel = buttonLabel
}
func drawButton() {
button.frame = 100, 100, buttonRadius * 2, buttonRadius * 2)
button.layer.borderColor = buttonColour
button.setTitle(buttonLabel, forState: UIControlState.Normal)
button.addTarget(self, action: "pressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
}
}
Swift class doesn't like self.view.addSubview()

Custom Class view and selector reference

I'm making a custom class that just has some basic functions I can reuse once the class is initialized in any viewController. The problem is I don't know how to refer to the view or any functions being called in the #selector and I get the error: use of unresolved identifier "view". Anyone know how that's done? The sample below is to create a new UIButton.
import Foundation
import UIKit
class Utils {
var newUpdatesBtn: UIButton!
func setupButtonNewUpdates() {
newUpdatesBtn = UIButton(type: UIButtonType.System) // .System
newUpdatesBtn.setTitle("New Updates", forState: UIControlState.Normal)
newUpdatesBtn.bounds = CGRect(x: 0, y: 0, width: 123, height: 27)
newUpdatesBtn.center = CGPoint(x: view.bounds.width / 2, y: 90) // 80 point button, 20 point status bar, 40 point half button, 8 point margins
newUpdatesBtn.setBackgroundImage(UIImage(named: "new-updates"), forState: UIControlState.Normal)
newUpdatesBtn.titleLabel?.font = UIFont.systemFontOfSize(14)
newUpdatesBtn.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
newUpdatesBtn.addTarget(self, action: #selector(newUpdatesButtonPressed), forControlEvents: UIControlEvents.TouchUpInside)
self.newUpdatesBtn.alpha = 0;
self.navigationController!.view.addSubview(newUpdatesBtn)
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.newUpdatesBtn.alpha = 1;
})
}
}
(problem lines)
view.bounds.width
newUpdatesBtn.addTarget(self, action: #selector(newUpdatesButtonPressed), forControlEvents: UIControlEvents.TouchUpInside)
This is because you have not set any reference in view property.
You can do like this:
func setupButtonNewUpdates(targetView:UIView)
{
newUpdatesBtn = UIButton(type: UIButtonType.System) // .System
.
.
.
newUpdatesBtn.center = CGPoint(x: targetView.bounds.width / 2, y: 90) // 80 point button, 20 point status bar, 40 point half button, 8 point margins
.
.
.
targetView.addSubView(newUpdatesBtn);
}
Now whenever you call this method from any viewcontroller just pass the view of that VC. for example:
var util:Utils = new Utils();
util.setupButtonNewUpdates(self.view);

How do i make a menu like Spotify

Ok as the question suggests, I want to make a menu like the spotify app currently have.
I have come across a code in github that resembles this but i want to know how to achieve this on a DIY(Do it yourself) level.
I can understand that a pageview controller is needed but am lost. Please point me to the right direction.
EDIT
So that people might understand properly the requirement I am talking about, Here is a png
Notice the top menu that slides with the underline as the selected menu.
The github link that i posted is a library that does that, but its in swift, so can anyone tell me as to how to achieve this in objective c
Regards
I have made a similar kind of sample but that is in Swift 2.0. Have a look. May be it can be of some help.
In the sample link posted below, the query you are asking can be done in the following way.
We can use this method to setup the buttons first on the top of page view
In your terms this is the Top Menu
func setupSegmentButtons(){
navigationView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, (self.navigationController?.navigationBar.frame.size.height)! - 4))
//navigationView.backgroundColor = UIColor.redColor()
navigationView.layoutIfNeeded()
let numControllers:NSInteger = viewControllerArray.count
if(buttonText == nil)
{
buttonText = NSArray(objects: "First","Second","Third")
}
let buttonWidth:CGFloat = self.view.frame.size.width/CGFloat(numControllers)
for (var i = 0; i < numControllers; i++) {
let button:UIButton = UIButton(frame: CGRectMake( CGFloat(i) * buttonWidth, 0, buttonWidth, 40))
button.addTarget(self, action: "tapSegmentButtonAction:", forControlEvents: .TouchUpInside)
navigationView.addSubview(button)
button.tag = i
button.titleLabel?.font = UIFont(name: "Helvetica", size: 14)
button.setTitleColor(UIColor.blackColor(), forState: .Normal)
button.setTitle(buttonText.objectAtIndex(i) as? String, forState: .Normal)
}
self.view.addSubview(navigationView)
self.setupSelector()
}
Now we have to add the selection bar which will slide accordingly with the menu selected.
Setting up the selection bar view
func setupSelector(){
//selection bar view
selectionBar = UIView(frame: CGRectMake(X_BUFFER - X_OFFSET + SELECTED_POSITION , SELECTOR_Y_BUFFER, (self.view.frame.size.width - 2 * X_BUFFER)/CGFloat(viewControllerArray.count), SELECTOR_HEIGHT))
selectionBar.backgroundColor = UIColor.brownColor()
selectionBar.alpha = 0.8
navigationView.addSubview(selectionBar)
}
Now we can use the scroll view's scrollViewDidScroll delegate in order to make the selection bar view slides towards the selected menu. Each time this delegate is called whenever the page view controller is scrolled, we change the frame of selected bar view every moment.
func scrollViewDidScroll(scrollView: UIScrollView) {
let xFromCenter : CGFloat = self.view.frame.size.width-scrollView.contentOffset.x;
let xCoors : CGFloat = X_BUFFER + selectionBar.frame.size.width * CGFloat(currentPageIndex) - X_OFFSET
_ = selectionBar.frame
if(CGFloat(xCoors)-xFromCenter / CGFloat(viewControllerArray.count) > self.view.frame.size.width - selectionBar.frame.width)
{
selectionBar.frame = CGRectMake(self.view.frame.size.width - selectionBar.frame.width, selectionBar.frame.origin.y, selectionBar.frame.size.width, selectionBar.frame.size.height);
}
else if(CGFloat(xCoors)-xFromCenter / CGFloat(viewControllerArray.count) < 0)
{
selectionBar.frame = CGRectMake(0, selectionBar.frame.origin.y, selectionBar.frame.size.width, selectionBar.frame.size.height);
}
else
{
selectionBar.frame = CGRectMake(CGFloat(xCoors)-xFromCenter/CGFloat(viewControllerArray.count), selectionBar.frame.origin.y, selectionBar.frame.size.width, selectionBar.frame.size.height);
}
}
For more details please see the sample link below.
https://github.com/RajanMaheshwari/PageViewController

How do I put the image on the right side of the text in a UIButton?

I don't want to use a subview if I can avoid it. I want a UIButton with a background image, text, and an image in it. Right now, when I do that, the image is on the left side of the text. The background image, text, and image all have different highlight states.
Simplest solution:
iOS 10 & up, Swift:
button.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
Before iOS 10, Swift/Obj-C:
button.transform = CGAffineTransformMakeScale(-1.0, 1.0);
button.titleLabel.transform = CGAffineTransformMakeScale(-1.0, 1.0);
button.imageView.transform = CGAffineTransformMakeScale(-1.0, 1.0);
iOS 9 & up, Swift: (Recommended)
button.semanticContentAttribute = .forceRightToLeft
Despite some of the suggested answers being very creative and extremely clever, the simplest solution is as follows:
button.semanticContentAttribute = UIApplication.shared
.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
As simple as that. As a bonus, the image will be at the left side in right-to-left locales.
EDIT: as the question has been asked a few times, this is iOS 9 +.
UPDATED FOR XCODE 9 (Via Interface Builder)
There's an easier way from the Interface Builder.
Select the UIButton and select this option in the View Utilities > Semantic:
That's it! Nice and simple!
OPTIONAL - 2nd step:
If you want to adjust the spacing between the image and the title you can change the Image Inset here:
Subclassing UIButton is completely unnecessary. Instead you can simply set a high left inset value for the image insets, and a small right inset for the title. Something like this:
button.imageEdgeInsets = UIEdgeInsetsMake(0., button.frame.size.width - (image.size.width + 15.), 0., 0.);
button.titleEdgeInsets = UIEdgeInsetsMake(0., 0., 0., image.size.width);
I'm giving Inspire48 the credit for this one. Based on his suggestion and looking at that other question I came up with this. Subclass UIButton and override these methods.
#implementation UIButtonSubclass
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
CGRect frame = [super imageRectForContentRect:contentRect];
frame.origin.x = CGRectGetMaxX(contentRect) - CGRectGetWidth(frame) - self.imageEdgeInsets.right + self.imageEdgeInsets.left;
return frame;
}
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
CGRect frame = [super titleRectForContentRect:contentRect];
frame.origin.x = CGRectGetMinX(frame) - CGRectGetWidth([self imageRectForContentRect:contentRect]);
return frame;
}
#end
Just update the insets when the title is changed. You need to compensate for the inset with an equal and opposite inset on the other side.
[thebutton setTitle:title forState:UIControlStateNormal];
thebutton.titleEdgeInsets = UIEdgeInsetsMake(0, -thebutton.imageView.frame.size.width, 0, thebutton.imageView.frame.size.width);
thebutton.imageEdgeInsets = UIEdgeInsetsMake(0, thebutton.titleLabel.frame.size.width, 0, -thebutton.titleLabel.frame.size.width);
All of these answers, as of January 2016, are unnecessary. In Interface Builder, set the View Semantic to Force Right-to-Left, or if you prefer programmatic way, semanticContentAttribute = .forceRightToLeft That will cause the image to appear on the right of your text.
In interface builder you can configure options Edge Insets for UIButton, separately each of three parts: content, image, title
Xcode 8:
I decided not to use the standard button image view because the proposed solutions to move it around felt hacky. This got me the desired aesthetic, and it is intuitive to reposition the button by changing the constraints:
extension UIButton {
func addRightIcon(image: UIImage) {
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
let length = CGFloat(15)
titleEdgeInsets.right += length
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: self.titleLabel!.trailingAnchor, constant: 10),
imageView.centerYAnchor.constraint(equalTo: self.titleLabel!.centerYAnchor, constant: 0),
imageView.widthAnchor.constraint(equalToConstant: length),
imageView.heightAnchor.constraint(equalToConstant: length)
])
}
}
Update: Swift 3
class ButtonIconRight: UIButton {
override func imageRect(forContentRect contentRect:CGRect) -> CGRect {
var imageFrame = super.imageRect(forContentRect: contentRect)
imageFrame.origin.x = super.titleRect(forContentRect: contentRect).maxX - imageFrame.width
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
}
return titleFrame
}
}
Original answer for Swift 2:
A solution that handles all horizontal alignments, with a Swift implementation example. Just translate to Objective-C if needed.
class ButtonIconRight: 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
}
}
Also worth noting that it handles quite well image & title insets.
Inspired from jasongregori answer ;)
If this need to be done in UIBarButtonItem, additional wrapping in view should be used
This will work
let view = UIView()
let button = UIButton()
button.setTitle("Skip", for: .normal)
button.setImage(#imageLiteral(resourceName:"forward_button"), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.sizeToFit()
view.addSubview(button)
view.frame = button.bounds
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: view)
This won't work
let button = UIButton()
button.setTitle("Skip", for: .normal)
button.setImage(#imageLiteral(resourceName:"forward_button"), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.sizeToFit()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
Do Yourself. Xcode10, swift4,
For programmatically UI design
lazy var buttonFilter : ButtonRightImageLeftTitle = {
var button = ButtonRightImageLeftTitle()
button.setTitle("Playfir", for: UIControl.State.normal)
button.setImage(UIImage(named: "filter"), for: UIControl.State.normal)
button.backgroundColor = UIColor.red
button.contentHorizontalAlignment = .left
button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
return button
}()
Edge inset values are applied to a rectangle to shrink or expand the
area represented by that rectangle. Typically, edge insets are used
during view layout to modify the view’s frame. Positive values cause
the frame to be inset (or shrunk) by the specified amount. Negative
values cause the frame to be outset (or expanded) by the specified
amount.
class ButtonRightImageLeftTitle: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
guard imageView != nil else { return }
imageEdgeInsets = UIEdgeInsets(top: 5, left: (bounds.width - 35), bottom: 5, right: 5)
titleEdgeInsets = UIEdgeInsets(top: 0, left: -((imageView?.bounds.width)! + 10), bottom: 0, right: 0 )
}
}
for StoryBoard UI design
iOS 15 brought an update where you can now handle image placements in buttons in a simpler non messier way, ie. without insets.
In XIB/Storyboards:
Simply set the button 'placement' property to leading/training/top/bottom after adding an image property to button. Since it's leading/training, there is an added advantage of it supporting RTL
**In code (Programmatically): **
Use Button Configuration property programmatically
This is not a backward compatible feature, and will work only in iOS15+ as was demonstrated in WWDC '21 - https://developer.apple.com/videos/play/wwdc2021/10064/?time=236
Developer documentation: https://developer.apple.com/documentation/uikit/uibutton/configuration?changes=_4
Here is solution for UIButton with center aligned content.
This code make image right aligned and allows to use imageEdgeInsets and titleEdgeInsets for precious positioning.
Subclass UIButton with your custom class and add:
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
CGRect frame = [super imageRectForContentRect:contentRect];
CGFloat imageWidth = frame.size.width;
CGRect titleRect = CGRectZero;
titleRect.size = [[self titleForState:self.state] sizeWithAttributes:#{NSFontAttributeName: self.titleLabel.font}];
titleRect.origin.x = (self.frame.size.width - (titleRect.size.width + imageWidth)) / 2.0 + self.titleEdgeInsets.left - self.titleEdgeInsets.right;
frame.origin.x = titleRect.origin.x + titleRect.size.width - self.imageEdgeInsets.right + self.imageEdgeInsets.left;
return frame;
}
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
CGFloat imageWidth = [self imageForState:self.state].size.width;
CGRect frame = [super titleRectForContentRect:contentRect];
frame.origin.x = (self.frame.size.width - (frame.size.width + imageWidth)) / 2.0 + self.titleEdgeInsets.left - self.titleEdgeInsets.right;
return frame;
}
Extension Way
Using extension to set image on the right side with custom offset
extension UIButton {
func addRightImage(image: UIImage, offset: CGFloat) {
self.setImage(image, for: .normal)
self.imageView?.translatesAutoresizingMaskIntoConstraints = false
self.imageView?.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
self.imageView?.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -offset).isActive = true
}
}
Being that the transform solution doesn't work in iOS 11 I decided to write a new approach.
Adjusting the buttons semanticContentAttribute gives us the image nicely to the right without having to relayout if the text changes. Because of this it's the ideal solution. However I still need RTL support. The fact that an app can not change it's layout direction in the same session resolves this issue easily.
With that said, it's pretty straight forward.
extension UIButton {
func alignImageRight() {
if UIApplication.shared.userInterfaceLayoutDirection == .leftToRight {
semanticContentAttribute = .forceRightToLeft
}
else {
semanticContentAttribute = .forceLeftToRight
}
}
}
Swift -Extend the UiButton and put these lines
if let imageWidth = self.imageView?.frame.width {
self.titleEdgeInsets = UIEdgeInsetsMake(0, -imageWidth, 0, imageWidth);
}
if let titleWidth = self.titleLabel?.frame.width {
let spacing = titleWidth + 20
self.imageEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, -spacing);
}
Building on Piotr Tomasik's elegant solution: if you want to have a bit of spacing between the button label and image as well, then include that in your edge insets as follows (copying my code here that works perfectly for me):
CGFloat spacing = 3;
CGFloat insetAmount = 0.5 * spacing;
// First set overall size of the button:
button.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
[button sizeToFit];
// Then adjust title and image insets so image is flipped to the right and there is spacing between title and image:
button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width - insetAmount, 0, button.imageView.frame.size.width + insetAmount);
button.imageEdgeInsets = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width + insetAmount, 0, -button.titleLabel.frame.size.width - insetAmount);
Thanks Piotr for your solution!
Erik
Took #Piotr's answer and made it into a Swift extension. Make sure to set the image and title before calling this, so that the button sizes properly.
extension UIButton {
/// Makes the ``imageView`` appear just to the right of the ``titleLabel``.
func alignImageRight() {
if let titleLabel = self.titleLabel, imageView = self.imageView {
// Force the label and image to resize.
titleLabel.sizeToFit()
imageView.sizeToFit()
imageView.contentMode = .ScaleAspectFit
// Set the insets so that the title appears to the left and the image appears to the right.
// Make the image appear slightly off the top/bottom edges of the button.
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -1 * imageView.frame.size.width,
bottom: 0, right: imageView.frame.size.width)
self.imageEdgeInsets = UIEdgeInsets(top: 4, left: titleLabel.frame.size.width,
bottom: 4, right: -1 * titleLabel.frame.size.width)
}
}
}
With Xcode 13.3 I solved in the following few steps and as well adding padding to the image.
After creating the button then do this as listed below:
First define the image:
let symbol = UIImage(named: "put name of your symbol here")
Then in viewDidLoad where you created the button, initialise the above defined image in 1, to add the image to the button & set the properties:
button.setImage(symbol, for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.configuration?.imagePadding = 2
And don't forget to add your button to the view.
Subclassing and over-riding layoutSubviews is probably your best way to go.
Referenced from: iPhone UIButton - image position
A swift option that does what you want without playing with any insets:
class RightImageButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
if let textSize = titleLabel?.intrinsicContentSize(),
imageSize = imageView?.intrinsicContentSize() {
let wholeWidth = textSize.width + K.textImageGap + imageSize.width
titleLabel?.frame = CGRect(
x: round(bounds.width/2 - wholeWidth/2),
y: 0,
width: ceil(textSize.width),
height: bounds.height)
imageView?.frame = CGRect(
x: round(bounds.width/2 + wholeWidth/2 - imageSize.width),
y: RoundRetina(bounds.height/2 - imageSize.height/2),
width: imageSize.width,
height: imageSize.height)
}
}
struct K {
static let textImageGap: CGFloat = 5
}
}
Solutions mentioned here stopped working, once I enabled Auto Layout. I had to come up with my own:
Subclass UIButton and override layoutSubviews method:
//
// MIThemeButtonImageAtRight.m
// Created by Lukasz Margielewski on 7/9/13.
//
#import "MIThemeButtonImageAtRight.h"
static CGRect CGRectByApplyingUIEdgeInsets(CGRect frame, UIEdgeInsets insets);
#implementation MIThemeButtonImageAtRight
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect contentFrame = CGRectByApplyingUIEdgeInsets(self.bounds, self.contentEdgeInsets);
CGRect frameIcon = self.imageView.frame;
CGRect frameText = self.titleLabel.frame;
frameText.origin.x = CGRectGetMinX(contentFrame) + self.titleEdgeInsets.left;
frameIcon.origin.x = CGRectGetMaxX(contentFrame) - CGRectGetWidth(frameIcon);
self.imageView.frame = frameIcon;
self.titleLabel.frame = frameText;
}
#end
static CGRect CGRectByApplyingUIEdgeInsets(CGRect frame, UIEdgeInsets insets){
CGRect f = frame;
f.origin.x += insets.left;
f.size.width -= (insets.left + insets.right);
f.origin.y += (insets.top);
f.size.height -= (insets.top + insets.bottom);
return f;
}
Result:
swift 3.0 Migration
solution given by jasongregori
class ButtonIconRight: UIButton {
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
var imageFrame = super.imageRect(forContentRect: contentRect)
imageFrame.origin.x = super.titleRect(forContentRect: contentRect).maxX - imageFrame.width
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
}
return titleFrame
}
Xcode 11.4 Swift 5.2
For anyone trying to mirror the Back button style with the chevron like this:
import UIKit
class NextBarButton: UIBarButtonItem {
convenience init(target: Any, selector: Selector) {
// Create UIButton
let button = UIButton(frame: .zero)
// Set Title
button.setTitle("Next", for: .normal)
button.setTitleColor(.systemBlue, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 17)
// Configure Symbol
let config = UIImage.SymbolConfiguration(pointSize: 19.0, weight: .semibold, scale: .large)
let image = UIImage(systemName: "chevron.right", withConfiguration: config)
button.setImage(image, for: .normal)
// Add Target
button.addTarget(target, action: selector, for: .touchUpInside)
// Put the Image on the right hand side of the button
// Credit to liau-jian-jie for this part
button.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
// Customise spacing to match system Back button
button.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: -18.0, bottom: 0.0, right: 0.0)
button.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -12.0, bottom: 0.0, right: 0.0)
self.init(customView: button)
}
}
Implementation:
override func viewDidLoad() {
super.viewDidLoad()
let nextButton = NextBarButton(target: self, selector: #selector(nextTapped))
navigationItem.rightBarButtonItem = nextButton
}
#objc func nextTapped() {
// your code
}
Swift 3:
open override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
var frame = super.imageRect(forContentRect: contentRect)
let imageWidth = frame.size.width
var titleRect = CGRect.zero
titleRect.size = self.title(for: self.state)!.size(attributes: [NSFontAttributeName: self.titleLabel!.font])
titleRect.origin.x = (self.frame.size.width - (titleRect.size.width + imageWidth)) / 2.0 + self.titleEdgeInsets.left - self.titleEdgeInsets.right;
frame.origin.x = titleRect.origin.x + titleRect.size.width - self.imageEdgeInsets.right + self.imageEdgeInsets.left;
return frame
}
open override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
var frame = super.titleRect(forContentRect: contentRect)
if let imageWidth = self.image(for: self.state)?.size.width {
frame.origin.x = (self.frame.size.width - (frame.size.width + imageWidth)) / 2.0 + self.titleEdgeInsets.left - self.titleEdgeInsets.right;
}
return frame
}
How about Constraints? Unlike semanticContentAttribute, they don't change semantics. Something like this perhaps:
button.rightAnchorconstraint(equalTo: button.rightAnchor).isActive = true
or in Objective-C:
[button.imageView.rightAnchor constraintEqualToAnchor:button.rightAnchor].isActive = YES;
Caveats: Untested, iOS 9+
After trying multiple solutions from around the internet, I was not achieving the exact requirement. So I ended up writing custom utility code. Posting to help someone in future.
Tested on swift 4.2
// This function should be called in/after viewDidAppear to let view render
func addArrowImageToButton(button: UIButton, arrowImage:UIImage = #imageLiteral(resourceName: "my_image_name") ) {
let btnSize:CGFloat = 32
let imageView = UIImageView(image: arrowImage)
let btnFrame = button.frame
imageView.frame = CGRect(x: btnFrame.width-btnSize-8, y: btnFrame.height/2 - btnSize/2, width: btnSize, height: btnSize)
button.addSubview(imageView)
//Imageview on Top of View
button.bringSubviewToFront(imageView)
}
for this issue you can create UIView inside "label with UIImage view" and set UIView class as a UIControl and create IBAction as tuch up in side
Swift 4 & 5
Change the direction of UIButton image (RTL and LTR)
extension UIButton {
func changeDirection(){
isArabic ? (self.contentHorizontalAlignment = .right) : (self.contentHorizontalAlignment = .left)
// left-right margin
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
}
}

Resources