I have a simple iOS app with various view controllers.
Each view controller has different functionality but each view controller has 'load' button, that when triggered, sending a request and getting a result to delegate method.
I want to use an UIActivityIndicatorView that will start when the user will click the button and will stop on the delegate method.
Obviously, I want the indicator to look the same on each VC, so I've made property of it, and on each viewDidLoad method I am using this code:
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
self.indicator.frame = CGRectMake(40.0, 20.0, 100.0, 100.0);
self.indicator.center = self.view.center;
The problem is, I am using the same parameters, on the same object, copping and pasting these lines on every view controller.
Let's say I want to change the style in the next version, I need to change it 10 times.
What would be the best way to use some kind of static indicator that would be set with these parameters and would be set on and off by demand?
Here is the one i use in swift 4.1
import UIKit
class ProgressView {
// MARK: - Variables
private var containerView = UIView()
private var progressView = UIView()
private var activityIndicator = UIActivityIndicatorView()
static var shared = ProgressView()
// To close for instantiation
private init() {}
// MARK: - Functions
func startAnimating(view: UIView = (UIApplication.shared.keyWindow?.rootViewController?.view)!) {
containerView.center = view.center
containerView.frame = view.frame
containerView.backgroundColor = UIColor(hex: 0xffffff, alpha: 0.5)
progressView.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
progressView.center = containerView.center
progressView.backgroundColor = UIColor(hex: 0x444444, alpha: 0.7)
progressView.clipsToBounds = true
progressView.cornerRadius = 10
activityIndicator.frame = CGRect(x: 0, y: 0, width: 60, height: 60)
activityIndicator.center = CGPoint(x: progressView.bounds.width/2, y: progressView.bounds.height/2)
activityIndicator.style = .whiteLarge
view.addSubview(containerView)
containerView.addSubview(progressView)
progressView.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
/// animate UIActivityIndicationView without blocking UI
func startSmoothAnimation(view: UIView = (UIApplication.shared.keyWindow?.rootViewController?.view)!) {
activityIndicator.frame = CGRect(x: 0, y: 0, width: 60, height: 60)
activityIndicator.center = view.center
activityIndicator.style = .whiteLarge
activityIndicator.color = UIColor.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
func stopAnimatimating() {
activityIndicator.stopAnimating()
containerView.removeFromSuperview()
}
}
extension UIColor {
convenience init(hex: UInt32, alpha: CGFloat) {
let red = CGFloat((hex & 0xFF0000) >> 16)/256.0
let green = CGFloat((hex & 0xFF00) >> 8)/256.0
let blue = CGFloat(hex & 0xFF)/256.0
self.init(red: red, green: green, blue: blue, alpha: alpha)
}
}
// usage
ProgressView.shared.startAnimating()
// to stop
ProgressView.shared.stopAnimatimating()
Hope it helps
I would suggest that you create a superclass to your view controllers and add the spinner functionality there, and let your view controllers inherit from it.
The superclass view controller would look something like this:
// .h-file
#interface SuperclassViewController : UIViewController
- (void)showIndicator;
- (void)hideIndicator;
#end
// .m file
#import "SuperclassViewController.h"
#interface SuperclassViewController ()
#property (nonatomic, strong) UIActivityIndicatorView *indicator;
#end
#implementation SuperclassViewController
- (void)viewDidLoad {
[super viewDidLoad];
_indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
self.indicator.frame = CGRectMake(40.0, 20.0, 100.0, 100.0);
self.indicator.layer.cornerRadius = 6;
self.indicator.center = self.view.center;
[self.indicator startAnimating];
}
- (void)showIndicator {
[self.view addSubview:self.indicator];
}
- (void)hideIndicator {
[self.indicator removeFromSuperview];
}
#end
Now, to inherit it do the following in your view controllers .h file:
#import "SuperclassViewController.h"
#interface YourViewController : SuperclassViewController;
/** properties and methods */
#end
Then you can call [self showIndicator] and [self hideIndicator] in your view controllers whenever needed without any extra coding.
You can create single view controller to display loading indicator in all view controller. You need to write code once, put following code in AppDelegate file.
Note: I'm not working in Objective-C, following code in Swift. So you need to transform code in objective C.
First add following code in ProgressVC:
ProgressVC.swift:
class func viewController() -> ProgressVC {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ProgressVC") as! ProgressVC
}
Add following code in your AppDelegate.
AppDelegate.swift:
var progressVC : ProgressVC?
static let shared = UIApplication.shared.delegate as! AppDelegate
func showLoading(isShow: Bool) {
if isShow {
// Remove progress view if already exist
if progressVC != nil {
progressVC?.view.removeFromSuperview()
progressVC = nil
}
progressVC = ProgressVC.viewController()
AppDelegate.shared.window?.addSubview((progressVC?.view)!)
} else {
if progressVC != nil {
progressVC?.view.removeFromSuperview()
}
}
}
Now, you need to call just above method with AppDelegate's shared instance. Enable animated property of UIActivityIndicatorView from storyboard.
Show:
AppDelegate.shared.showLoading(isShow: true)
Hide:
AppDelegate.shared.showLoading(isShow: false)
Screenshot:
You could create the activity indicator in the UIWindow and then you could show/hide it from any UIViewController.
To get the window use:
UIApplication.shared.keyWindow
Thank you all for your assistance,
I decided to make a singleton class that has a variable of UIActivityIndicatorView.
This is the declaration of the class:
#import "ProgressView.h"
#interface ProgressView()
#property (nonatomic) UIActivityIndicatorView *indicator;
+(ProgressView *)shared;
#end
#implementation ProgressView
+ (ProgressView *)shared {
static ProgressView* sharedVC = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedVC = [[self alloc] init];
});
return sharedVC;
}
- (instancetype)init {
self = [super init];
if (self) {
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
self.indicator.frame = CGRectMake(40.0, 20.0, 100.0, 100.0);
}
return self;
}
- (void)startAnimation:(UIView *)view {
self.indicator.center = view.center;
self.indicator.hidden = NO;
[self.indicator startAnimating];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 12 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if ([self.indicator isAnimating])
[self stopAnimation];
});
[view addSubview:self.indicator];
}
- (void)stopAnimation {
if ([self.indicator isAnimating]) {
[self.indicator stopAnimating];
[self.indicator removeFromSuperview];
}
}
#end
Please note I have added a rule that if the indicator didn't get triggered to stop in 12 seconds the class would stop the indicator by itself.
Now, all I have to do is to add this line in every place in my code where I would like to start the indicator:
[[ProgressView shared] startAnimation:self.view];
And to add this line to stop it:
[[ProgressView shared] stopAnimation];
I'm using the JBChartView library to draw a bar chart in my iOS application. Each Bar should have an identifier, that is shown at the bottom of the bar.
I tried defining a custom barView with an additional label, but do not know how to place the label relative to the bar.
Here's the code:
func barChartView(barChartView: JBBarChartView!, barViewAtIndex index: UInt) -> UIView! {
let barView = UIView()
// setting up the bar
let bar: Float = chartData[Int(index)]
var barColor = UIColorFromRGB(0xE8E8E8)
if bar >= 1 { barColor = UIColorFromRGB(0xFF6259) }
if bar > 33 { barColor = UIColorFromRGB(0xFFC02F) }
if bar > 66 { barColor = UIColorFromRGB(0x28CA41) }
barView.backgroundColor = barColor
// setting up the label
var label = UILabel()
label.textAlignment = NSTextAlignment.Center
label.textColor = barColor
label.text = NSString(format: "%.0f", bar)
barview.addSubview(label)
return barView
}
Your help would be much appreciated!
ok, this is embarassing: label and bar had the same color, so the label was there all the time .
The final code (improved and with an additional Data Label):
func barChartView(barChartView: JBBarChartView!, barViewAtIndex index: UInt) -> UIView! {
let barView = BarChartBarView()
let bar: Float = chartData[Int(index)]
var barColor = UIColorFromRGB(0xE8E8E8)
if bar >= 1 { barColor = UIColorFromRGB(0xFF6259) }
if bar > 33 { barColor = UIColorFromRGB(0xFFC02F) }
if bar > 66 { barColor = UIColorFromRGB(0x28CA41) }
barView.backgroundColor = barColor
barView.dataLabel.text = NSString(format: "%.0f", bar)
barView.legendLabel.text = chartLegend[Int(index)]
return barView
}
class BarChartBarView: UIView {
let labelFont = UIFont(name:"Raleway-Thin", size:8.0)
var padding = CGFloat(0)
var barWidth = CGFloat(27)
var dataLabel = UILabel()
var legendLabel = UILabel()
var legendLabelWidth = CGFloat(50)
var labelHeight = CGFloat(27)
override convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColorFromRGB(0xE8E8E8)
// setting up data Label
dataLabel.frame = CGRectMake(0, 0, barWidth, labelHeight)
dataLabel.textAlignment = NSTextAlignment.Center
dataLabel.textColor = UIColor.whiteColor()
dataLabel.font = labelFont
self.addSubview(dataLabel)
// setting up legend Label
legendLabel.frame = CGRectMake(0, self.bounds.height, legendLabelWidth, labelHeight)
legendLabel.textAlignment = NSTextAlignment.Center
legendLabel.textColor = UIColor.blackColor()
legendLabel.font = labelFont
self.addSubview(legendLabel)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
let xOffset = (self.barWidth - self.legendLabelWidth) / 2
let yOffset:CGFloat = self.bounds.height
let width = self.legendLabelWidth
let height = self.labelHeight
self.legendLabel.frame = CGRectMake(xOffset, yOffset, width, height)
}
}
#Armin
I got the clue to implement the following method:
- (UIView *)barChartView:(JBBarChartView *)barChartView barViewAtIndex:(NSUInteger)index
From Armin. Because even though JawBone's JBBarChart library is impressive their Demo is totally done using code, and also it's bit confusing at a glance. However my way of adding the Label view is same old way of creating a UIView and adding the UILabel as a subview and returning it. And it looks like follows:
- (UIView *)barChartView:(JBBarChartView *)barChartView barViewAtIndex:(NSUInteger)index {
UIView *barView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 55, (CGFloat)chartData[(int)index])];
if (index == 0) {
[barView setBackgroundColor:[UIColor grayColor]];
} else if (index == 1) {
[barView setBackgroundColor:[UIColor redColor]];
} else if (index == 2) {
[barView setBackgroundColor:[UIColor orangeColor]];
} else {
[barView setBackgroundColor:[UIColor blueColor]];
}
int roomCount = (int)chartData[(int)index];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(2, 5.0f, 50.0f, 21.0f)];
[label setTextAlignment:NSTextAlignmentCenter];
[label setTextColor:[UIColor blackColor]];
[label setFont:[UIFont fontWithName:#"Raleway-Thin" size:6.0]];
[label setText:[NSString stringWithFormat:#"%d/56", roomCount]];
[barView addSubview:label];
return barView;
}
Please consider that chartData is contains my data, as integers. So I need the height of each Bar View according to that integer in each position.
Hope this answer would be helpful to someone out there, specially who is juggling to do this implementation in Objective-C.
Cheers!
I have some trouble with my custom inputView for UITextFields. Depending on the text the user needs to input in a UITextField, the inputView displays only the needed letters. That means for short texts, an inputView with only one line of letters is sufficient, longer texts may require 2 or even 3 lines so the height of the inputView is variabel.
Since I was expecting better performance, there exists only one inputView instance that is used by every textField. That way the creation must only happen once and it made the sometimes needed direct access to the inputView easier. The inputView is set up in - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField, sets its required height and will be shown.
That works perfectly, but not on iOS8. There some system view containing the inputView will not update its frame to match the inputView's bounds when they are changed (first time works).
I know that can be fixed by using one instance of my inputView per textField. But I'm asking if there is a recommended/better way to adjust the frame or to report its change to the containing view. Maybe it is an iOS8 bug that could be fixed until release?
Here's some example code to reproduce the issue:
CustomInputView
#implementation CustomInputView
+ (CustomInputView*)sharedInputView{
static CustomInputView *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[CustomInputView alloc] init];
});
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
self.backgroundColor = [UIColor greenColor];
}
return self;
}
- (void)setupForTextField:(UITextField*)textField{
CGFloat height;
if(textField.tag == 1){
height = 100;
}else height = 50;
self.frame = CGRectMake(0, 0, 320, height);
}
#end
TestViewController code
- (void)viewDidLoad
{
[super viewDidLoad];
UITextField *tf = [[UITextField alloc] initWithFrame:CGRectMake(15, 50, 290, 30)];
tf.text = #"bigKeyboard";
tf.inputView = [CustomInputView sharedInputView];
tf.layer.borderWidth = 1;
tf.layer.borderColor = [UIColor lightGrayColor].CGColor;
tf.delegate = self;
tf.tag = 1;
[self.view addSubview:tf];
tf = [[UITextField alloc] initWithFrame:CGRectMake(15, 100, 290, 30)];
tf.text = #"smallKeyboard";
tf.inputView = [CustomInputView sharedInputView];
tf.layer.borderWidth = 1;
tf.layer.borderColor = [UIColor lightGrayColor].CGColor;
tf.delegate = self;
tf.tag = 2;
[self.view addSubview:tf];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:#"dismissKeyboard" forState:UIControlStateNormal];
[button addTarget:self action:#selector(endEditing) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(15, 150, 290, 30);
[self.view addSubview:button];
}
- (void)endEditing{
[self.view endEditing:YES];
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
[[CustomInputView sharedInputView] setupForTextField:textField];
return YES;
}
I had similar issues with sizing a custom keyboard from iOS 8 to iOS 10. I believe the proper solution is to have the input view provide a proper intrinsicContentSize and change (and invalidate!) that value when you want to change the view's height. Sample code:
class CustomInputView: UIInputView {
var intrinsicHeight: CGFloat = 200 {
didSet {
self.invalidateIntrinsicContentSize()
}
}
init() {
super.init(frame: CGRect(), inputViewStyle: .keyboard)
self.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.translatesAutoresizingMaskIntoConstraints = false
}
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: self.intrinsicHeight)
}
}
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.becomeFirstResponder()
let inputView = CustomInputView()
// To make the view's size more clear.
inputView.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0.5, alpha: 1)
textView.inputView = inputView
// To demonstrate a change to the view's intrinsic height.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
inputView.intrinsicHeight = 400
}
}
}
See also https://stackoverflow.com/a/40359382/153354.
Another thing which I found critical for resizing an inputView on iOS 9 and up is setting allowsSelfSizing to true:
if #available(iOS 9.0, *) {
self.inputView?.allowsSelfSizing = true
}
I want to make the placeholder text display in middle of the textfield (Padding placeholder text). The size of the placeholder text also needs to increase. My code is as follows, how can i solve this ?
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(30, 220,250,55)];
textField.placeholder=#"iiiiiii";
UIView *padView = [[UIView alloc] initWithFrame:CGRectMake(10, 110, 10, 0)];
textField.leftView = padView;
textField.leftViewMode = UITextFieldViewModeAlways;
[self.view addSubview:textField];
UPDATE
I want the font size of the placeholder text to increase your name, and it should have a Left padding to it.
You could subclass your UITextFiled and override methods:
MyTextField.m
- (CGRect)textRectForBounds:(CGRect)bounds {
return [self rectForBounds:bounds];
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [self rectForBounds:bounds];
}
- (CGRect)placeholderRectForBounds:(CGRect)bounds {
return [self rectForBounds:bounds];
}
//here 40 - is your x offset
- (CGRect)rectForBounds:(CGRect)bounds {
return CGRectInset(bounds, 40, 3);
}
upd:
also set
textFiled.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
because it could some problems with io6 vs ios 7 vertical positionning
Same question was being asked and answered as below Set padding for UITextField with UITextBorderStyleNone
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 20)];
textField.leftView = paddingView;
textField.leftViewMode = UITextFieldViewModeAlways;
You can set the starting text alignment (of the textField from xib or via code) to be center aligned.
Then in the -textFieldShouldBeginEditing, you can set the textField to be left aligned.
Similarly, on the -textFieldDidEndEditing, check if the textField is empty and if it is then set textField back to center aligned.
basically:
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
[textField setTextAlignment:NSTextAlignmentLeft];
return YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
if(textField.text.length == 0) {
[textField setTextAlignment:NSTextAlignmentCenter];
}
}
EDIT::
the .h of your ViewController class should look like:
#interface ViewController : UIViewController <UITextFieldDelegate>
{
UITextField *myTextField;
}
now, replace your other code with this:
myTextField = [[UITextField alloc] initWithFrame:CGRectMake(30, 220,250,55)];
myTextField.placeholder=#"iiiiiii";
//important
[myTextField setDelegate: self];
//commented lines not really needed
//UIView *padView = [[UIView alloc] initWithFrame:CGRectMake(10, 110, 10, 0)];
//textField.leftView = padView;
//textField.leftViewMode = UITextFieldViewModeAlways;
[self.view addSubview:textField];
sampleTextfield.leftView = UIView(frame: CGRectMake(0, 0, 20, self.sampleTextfield.frame.height))
sampleTextfield.leftViewMode = UITextFieldViewMode.Always
The easiest way that I found to do this task on swift 2 and Xcode 7:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let paddingView = UIView(frame: CGRectMake(0, 0, 25, self.emailTextField.frame.height))
emailTextField.leftView = paddingView
emailTextField.leftViewMode = UITextFieldViewMode.Always
}
}
func placeholderPadding(textField:UITextField, leftPadding:CGFloat) {
textField.leftView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: leftPadding, height: textField.frame.height))
textField.leftViewMode = UITextFieldViewMode.always
}
While I have not specifically tested it, this should work:
self.YourTextField.attributedPlaceholder = NSAttributedString(string: " YourText")
Add the amount of blank spaces (padding) to your string
The leftView and rightView views of an UITextField on iOS7 are really close to the textfield border.
How may I add some (horizontal) padding to those items?
I tried modifying the frame, but did not work
uint padding = 10;//padding for iOS7
UIImageView * iconImageView = [[UIImageView alloc] initWithImage:iconImage];
iconImageView.frame = CGRectMake(0 + padding, 0, 16, 16);
textField.leftView = iconImageView;
Please, note that I'm not interested in adding padding to the textfield's text, like this Set padding for UITextField with UITextBorderStyleNone
A much simpler solution, which takes advantage of contentMode:
arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"down_arrow"]];
arrow.frame = CGRectMake(0.0, 0.0, arrow.image.size.width+10.0, arrow.image.size.height);
arrow.contentMode = UIViewContentModeCenter;
textField.rightView = arrow;
textField.rightViewMode = UITextFieldViewModeAlways;
In Swift 3,
let arrow = UIImageView(image: UIImage(named: "arrowDrop"))
if let size = arrow.image?.size {
arrow.frame = CGRect(x: 0.0, y: 0.0, width: size.width + 10.0, height: size.height)
}
arrow.contentMode = UIViewContentMode.center
self.textField.rightView = arrow
self.textField.rightViewMode = UITextFieldViewMode.always
Was just working on this myself and used this solution:
- (CGRect) rightViewRectForBounds:(CGRect)bounds {
CGRect textRect = [super rightViewRectForBounds:bounds];
textRect.origin.x -= 10;
return textRect;
}
This will move the image over from the right by 10 instead of having the image squeezed up against the edge in iOS 7.
Additionally, this was in a subclass of UITextField, which can be created by:
Create a new file that's a subclass of UITextField instead of the default NSObject
Add a new method named - (id)initWithCoder:(NSCoder*)coder to set the image
- (id)initWithCoder:(NSCoder*)coder {
self = [super initWithCoder:coder];
if (self) {
self.clipsToBounds = YES;
[self setRightViewMode:UITextFieldViewModeUnlessEditing];
self.leftView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"textfield_edit_icon.png"]];
}
return self;
}
You may have to import #import <QuartzCore/QuartzCore.h>
Add the rightViewRectForBounds method above
In Interface Builder, click on the TextField you would like to subclass and change the class attribute to the name of this new subclass
Easiest way is add a UIView to leftView/righView and add an ImageView to UIView , adjust the origin of ImageView inside UIView anywhere you like , this worked for me like a charm. It needs only few lines of code
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 5, 26, 26)];
imgView.image = [UIImage imageNamed:#"img.png"];
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
[paddingView addSubview:imgView];
[txtField setLeftViewMode:UITextFieldViewModeAlways];
[txtField setLeftView:paddingView];
This works great for Swift:
let imageView = UIImageView(image: UIImage(named: "image.png"))
imageView.contentMode = UIViewContentMode.Center
imageView.frame = CGRectMake(0.0, 0.0, imageView.image!.size.width + 20.0, imageView.image!.size.height)
textField.rightViewMode = UITextFieldViewMode.Always
textField.rightView = imageView
This works for me
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 20)];
self.passwordTF.leftView = paddingView;
self.passwordTF.leftViewMode = UITextFieldViewModeAlways;
May it helps you.
I like this solution because it solves the problem with a single line of code
myTextField.layer.sublayerTransform = CATransform3DMakeTranslation(10.0f, 0.0f, 0.0f);
Note: .. or 2 if you consider including QuartzCore a line :)
Swift 5
class CustomTextField: UITextField {
func invalidate() {
let errorImage = UIImageView(image: UIImage(named: "errorImage"))
errorImage.frame = CGRect(x: 8, y: 8, width: 16, height: 16)
rightView = UIView(frame: CGRect(x: 0, y: 0, width: 32, height: 32))
rightView?.addSubview(errorImage)
rightViewMode = .always
}
}
You'll want to:
Subclass UITextField
Write an invalidate method inside the
subclassed text field
In the invalidate method, create a UIView
larger than your image
Place your image inside the view
Assign the
view to UITextField.rightView
Instead of manipluating imageView or image we can override a method provided by apple for rightView.
class CustomTextField : UITextField {
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
let offset = 5
let width = 20
let height = width
let x = Int(bounds.width) - width - offset
let y = offset
let rightViewBounds = CGRect(x: x, y: y, width: width, height: height)
return rightViewBounds
}}
and same way we can override below func for left view.
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
/*return as per requirement*/
}
The best way to do this is simply make a class using subclass of UITextField and in .m file
#import "CustomTextField.h"
#import <QuartzCore/QuartzCore.h>
#implementation CustomTextField
- (id)initWithCoder:(NSCoder*)coder
{
self = [super initWithCoder:coder];
if (self) {
//self.clipsToBounds = YES;
//[self setRightViewMode:UITextFieldViewModeUnlessEditing];
self.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,15,46)];
self.leftViewMode=UITextFieldViewModeAlways;
}
return self;
}
by doing this go to your storyboard or xib and click on identity inspector and replace UITextfield with your own "CustomTextField" in class option.
Note: If you simply give padding with auto layout for textfield then your application will not run and show only blank screen.
I found this somewhere...
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 20)];
paddingView.backgroundColor = [UIColor clearColor];
itemDescription.leftView = paddingView;
itemDescription.leftViewMode = UITextFieldViewModeAlways;
[self addSubview:itemDescription];
Since iOS 13 and Xcode 11 this is the only solution that works for us.
// Init of custom UITextField
override init(frame: CGRect) {
super.init(frame: frame)
if let size = myButton.imageView?.image?.size {
myButton.frame = CGRect(x:0, y: 0, width: size.width, height: size.height)
let padding: CGFloat = 5
let container = UIView(frame: CGRect(x:0, y: 0, width: size.width + padding, height: size.height))
container.addSubview(myButton)
myButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
myButton.topAnchor.constraint(equalTo: container.topAnchor),
myButton.leftAnchor.constraint(equalTo: container.leftAnchor),
myButton.bottomAnchor.constraint(equalTo: container.bottomAnchor),
myButton.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -padding),
])
textField.rightViewMode = .always
textField.rightView = container
}
}
Maybe you might set up an empty view and embed your view as a subview:
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 50.0, height: 50.0))
imageView.contentMode = .center
imageView.image = UIImage(named: "ic_dropdown")
let emptyView = UIView(frame: CGRect(x: 0, y: 0, width: 50.0, height: 50.0))
emptyView.backgroundColor = .clear
emptyView.addSubview(imageView)
self.documentTypeTextLabel.rightView = emptyView
self.documentTypeTextLabel.rightViewMode = .always
Happy coding
Create a custom UITextField class and use that class instead of UITextField. Override - (CGRect) textRectForBounds:(CGRect)bounds to set the rect that you need
Example
- (CGRect)textRectForBounds:(CGRect)bounds{
CGRect textRect = [super textRectForBounds:bounds];
textRect.origin.x += 10;
textRect.size.width -= 10;
return textRect;
}
Here is one solution:
UIView *paddingTxtfieldView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, 42)]; // what ever you want
txtfield.leftView = paddingTxtfieldView;
txtfield.leftViewMode = UITextFieldViewModeAlways;
Below example is for adding horizontal padding to a left view that happens to be an icon - you can use the similar approach for adding padding to any UIView that you would like to use as the textfield's left view.
Inside UITextField subclass:
static CGFloat const kLeftViewHorizontalPadding = 10.0f;
#implementation TextFieldWithLeftIcon
{
UIImage *_image;
UIImageView *_imageView;
}
- (instancetype)initWithFrame:(CGRect)frame image:(UIImage *)image
{
self = [super initWithFrame:frame];
if (self) {
if (image) {
_image = image;
_imageView = [[UIImageView alloc] initWithImage:image];
_imageView.contentMode = UIViewContentModeCenter;
self.leftViewMode = UITextFieldViewModeAlways;
self.leftView = _imageView;
}
}
return self;
}
#pragma mark - Layout
- (CGRect)leftViewRectForBounds:(CGRect)bounds
{
CGFloat widthWithPadding = _image.size.width + kLeftViewHorizontalPadding * 2.0f;
return CGRectMake(0, 0, widthWithPadding, CGRectGetHeight(bounds));
}
Although we are a subclassing UITextField here, I believe this is the cleanest approach.
- (CGRect)rightViewRectForBounds:(CGRect)bounds
{
return CGRectMake(bounds.size.width - 40, 0, 40, bounds.size.height);
}
thank you guys for your answers, to my surprise none of them really fitted the right view image to my textfield while still providing the needed padding. then i thought of using the AspectFill mode and miracles happened. for future seekers, here's what i used:
UIImageView *emailRightView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 35, 35)];
emailRightView.contentMode = UIViewContentModeScaleAspectFill;
emailRightView.image = [UIImage imageNamed:#"icon_email.png"];
emailTextfield.rightViewMode = UITextFieldViewModeAlways;
emailTextfield.rightView = emailRightView;
the 35 in the frame of my imageview represents the height of my emailTextfield, feel free to adjust it to your needs.
If you are using a UIImageView as leftView then you have to use this code :
Caution : Don't use inside viewWillAppear or viewDidAppear
-(UIView*)paddingViewWithImage:(UIImageView*)imageView andPadding:(float)padding
{
float height = CGRectGetHeight(imageView.frame);
float width = CGRectGetWidth(imageView.frame) + padding;
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
[paddingView addSubview:imageView];
return paddingView;
}
I created a custom method in my ViewController class, like shown bellow:
- (void) modifyTextField:(UITextField *)textField
{
// Prepare the imageView with the required image
uint padding = 10;//padding for iOS7
UIImageView * iconImageView = [[UIImageView alloc] initWithImage:iconImage];
iconImageView.frame = CGRectMake(0 + padding, 0, 16, 16);
// Set the imageView to the left of the given text field.
textField.leftView = iconImageView;
textField.leftViewMode = UITextFieldViewModeAlways;
}
Now I can call that method inside (viewDidLoad method) and send any of my TextFields to that method and add padding for both right and left, and give text and background colors by writing just one line of code, as follows:
[self modifyTextField:self.firstNameTxtFld];
This Worked perfectly on iOS 7! Hope this still works on iOS 8 and 9 too!
I know that adding too much Views might make this a bit heavier object to be loaded. But when concerned about the difficulty in other solutions, I found myself more biased to this method and more flexible with using this way. ;)
Hope this answer might be helpful or useful to figure out another solution to someone else.
Cheers!
This works for me just like I looking for:
func addImageViewInsideMyTextField() {
let someView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 24))
let imageView = UIImageView(image: UIImage(named: "accountImage"))
imageView.frame = CGRect(x: 16, y: 0, width: 24, height: 24)
imageView.contentMode = .scaleAspectFit
someView.addSubview(imageView)
self.myTextField.leftView = someView
self.myTextField.leftViewMode = .always
}
Set Rightview of UITextField using swift 4.2
TxtPass.rightViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 18, height: 18))
imageView.contentMode = UIView.ContentMode.scaleAspectFit
let image = UIImage(named: "hidepass")
imageView.image = image
let rightView = UIView(frame: CGRect(x: 0, y: 0, width: 28, height: 18))
rightView.addSubview(imageView)
rightView.contentMode = UIView.ContentMode.left
TxtPass.rightView = rightView
One trick: Add a UIView containing UIImageView to UITextField as rightView. This UIView must be larger in size, now place the UIImageView to left of it. So there will be a padding of space from right.
// Add a UIImageView to UIView and now this UIView to UITextField - txtFieldDate
UIView *viewRightIntxtFieldDate = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, 30)];
// (Height of UITextField is 30px so height of viewRightIntxtFieldDate = 30px)
UIImageView *imgViewCalendar = [[UIImageView alloc] initWithFrame:CGRectMake(0, 10, 10, 10)];
[imgViewCalendar setImage:[UIImage imageNamed:#"calendar_icon.png"]];
[viewRightIntxtFieldDate addSubview:imgViewCalendar];
txtFieldDate.rightViewMode = UITextFieldViewModeAlways;
txtFieldDate.rightView = viewRightIntxtFieldDate;
I have had this problem myself, and by far the easiest solution is to modify your image to simply add padding to each side of the image!
I just altered my png image to add 10 pixels transparent padding, and it works well, with no coding at all!
Easiest way is just change the Textfield as RoundRect instead of Custom and see the magic. :)
for Swift2 , I use
...
self.mSearchTextField.leftViewMode = UITextFieldViewMode.Always
let searchImg = UIImageView(image: UIImage(named: "search.png"))
let size = self.mSearchTextField.frame.height
searchImg.frame = CGRectMake(0, 0, size,size)
searchImg.contentMode = UIViewContentMode.ScaleAspectFit
self.mSearchTextField.leftView = searchImg
...
...
textField.rightView = UIImageView(image: ...)
textField.rightView?.contentMode = .top
textField.rightView?.bounds.size.height += 10
textField.rightViewMode = .always
...
I realize this an old post and this answer is a bit specific to my use case, but I posted it in case others are seeking a similar solution. I want to move a UITextField's leftView or rightView but I am not putting images in them and do not want any hard coded constants.
My UI calls for hiding the text field's clear button and displaying a UIActivityIndicatorView where the clear button was located.
I add a spinner to the rightView, but out of the box (on iOS 13) it is shifted 20 pixels to the right of the clearButton. I don't like to use magic numbers since the position of the clearButton and rightView are subject to change at any time by Apple. The UI design intent is "spinner where the clear button is" so my solution was to subclass UITextField and override rightViewRect(forBounds).
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
// Use clearButton's rectangle
return self.clearButtonRect(forBounds: bounds)
}
Below is a working example (sans Storyboard):
//------------------------------------------------------------------------------
class myCustomTextField: UITextField {
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
// Use clearButton rectangle
return self.clearButtonRect(forBounds: bounds)
}
}
//------------------------------------------------------------------------------
class myViewController: UIViewController {
var activityView: UIActivityIndicatorView = {
let activity = UIActivityIndicatorView()
activity.startAnimating()
return activity
}()
#IBOutlet weak var searchTextField: myCustomTextField!
//------------------------------------------------------------------------------
// MARK: - Lifecycle
//------------------------------------------------------------------------------
override func viewDidLoad() {
super.viewDidLoad()
searchTextField.rightView = activityView
searchTextField.rightViewMode = .never // Hide spinner
searchTextField.clearButtonMode = .never // Hide clear button
setupUIForTextEntry()
}
// ...
// More code to switch between user text entry and "search progress"
// by calling setupUI... functions below
// ...
//------------------------------------------------------------------------------
// MARK: - UI
//------------------------------------------------------------------------------
func setupUIForTextEntry() {
// Hide spinner
searchTextField.rightViewMode = .never
// Show clear button
searchTextField.clearButtonMode = .whileEditing
searchTextField.becomeFirstResponder()
}
//------------------------------------------------------------------------------
func setupUIForSearching() {
// Show spinner
searchTextField.rightViewMode = .always
// Hide clear button
searchTextField.clearButtonMode = .never
searchTextField.resignFirstResponder()
}
//------------------------------------------------------------------------------
}
//------------------------------------------------------------------------------
Simple approach:
textField.rightViewMode = .always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 25, height: 15))
textField.contentMode = .scaleAspectFit
imageView = UIImage(named: "imageName")
textField.rightView = imageView
Note: Height should be smaller than the width to allow horizontal padding.