How to get resizing of UILabel objects with constraints to animate smoothly? - ios

I have a vertical rectangle -- a simple UIView -- that is divided into 4 sections, sort of like a pie chart, and each section will grow and shrink dynamically (as data rolls in), and I'm trying to get that to happen smoothly. Am using constraints to keep their sides united tightly to one another.
Part of the animation happens smoothly, but initially the four colored sections, which are just empty UILabel objects, are resized abruptly, revealing the background color of the container and then the animation seems to kick in and resolve the boundaries of the UILabel objects smoothly. I have a good video captured from the Simulator that shows the behavior, but don't have a way to provide that in the question here. Link perhaps coming later. The animation code right now is very simple. When a timer fires I simply alternate between two different states wherein I assign the constant value for the height constraints. Like so:
-(void)relayoutSubviewsAnimated {
static int ctr = 1;
[self layoutIfNeeded];
[UIView animateWithDuration:1.5
animations:^{
if (ctr == 1) {
self->_outletBucketMastersHeight.constant = 0.25 * nHeightOfPieBar;
self->_outletBucketMeetsHeight.constant = 0.25 * nHeightOfPieBar;
self->_outletBucketApproachesHeight.constant = 0.25 * nHeightOfPieBar;
self->_outletBucketDidNotMeetHeight.constant = 0.25 * nHeightOfPieBar;
ctr++;
}
else if (ctr == 2) {
self->_outletBucketMastersHeight.constant = 0.2 * nHeightOfPieBar;
self->_outletBucketMeetsHeight.constant = 0.1 * nHeightOfPieBar;
self->_outletBucketApproachesHeight.constant = 0.4 * nHeightOfPieBar;
self->_outletBucketDidNotMeetHeight.constant = 0.4 * nHeightOfPieBar;
ctr = 1;
}
[self layoutIfNeeded];
}];
}
So, initially the sections will resize suddenly, with no animation, and will momentarily look like so:
but will then smoothly animate the sizes until everything looks correct, like so:
The other usual constraints (horizontal and vertical space constraints) bind the UILabel objects to each other and leading and trailing constraints bind the UILabel objects to the sides of their container.
What could I be doing wrong? How do I smoothly animate the growth and shrinkage of these 4 UILabels without the white background of the container suddenly showing through? I have read a number of SO questions and other articles.

As I mentioned in my comments, this is what I would call a BUG.
When we animate the height of a UILabel:
if it's getting taller, no problem
if it's getting shorter, it snaps to the shorter height
Quick demonstration:
class V1_LabelHeightAnimVC: UIViewController {
let testLabel = UILabel()
let testView = UIView()
let embeddedLabelView = UIView()
var tlh: NSLayoutConstraint!
var tvh: NSLayoutConstraint!
var elvh: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
testLabel.text = "ABC"
testLabel.textColor = .yellow
testLabel.textAlignment = .center
testView.backgroundColor = .red
testLabel.backgroundColor = .blue
testView.translatesAutoresizingMaskIntoConstraints = false
testLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testView)
view.addSubview(testLabel)
let v = UILabel()
v.backgroundColor = .yellow
v.text = "ABC"
embeddedLabelView.backgroundColor = .systemBlue
v.translatesAutoresizingMaskIntoConstraints = false
embeddedLabelView.addSubview(v)
embeddedLabelView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(embeddedLabelView)
tvh = testView.heightAnchor.constraint(equalToConstant: 300.0)
tlh = testLabel.heightAnchor.constraint(equalToConstant: 300.0)
elvh = embeddedLabelView.heightAnchor.constraint(equalToConstant: 300.0)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testView.widthAnchor.constraint(equalToConstant: 60.0),
testLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
testLabel.leadingAnchor.constraint(equalTo: testView.trailingAnchor, constant: 40.0),
testLabel.widthAnchor.constraint(equalToConstant: 60.0),
v.centerXAnchor.constraint(equalTo: embeddedLabelView.centerXAnchor),
v.centerYAnchor.constraint(equalTo: embeddedLabelView.centerYAnchor),
embeddedLabelView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
embeddedLabelView.leadingAnchor.constraint(equalTo: testLabel.trailingAnchor, constant: 40.0),
embeddedLabelView.widthAnchor.constraint(equalToConstant: 60.0),
tvh, tlh, elvh,
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tvh.constant = tvh.constant == 300.0 ? 100.0 : 300.0
tlh.constant = tlh.constant == 300.0 ? 100.0 : 300.0
elvh.constant = elvh.constant == 300.0 ? 100.0 : 300.0
UIView.animate(withDuration: 1.0, animations: {
self.view.layoutIfNeeded()
})
}
}
It looks like this when running:
Tapping anywhere will toggle the Height constraint constants between 300 and 100 and animate to the new values.
the Red rectangle is a UIView ... it animates as expected
the dark Blue rectangle is a UILabel ... you'll see it snap
the light Blue rectangle is a UIView with a UILabel as a subview. It gives us the desired animations.
Here's an example to achieve your layout, using a simple UIView subclass to hold the "centered" labels:
class EmbeddedLabelView: UIView {
var text: String = "" {
didSet {
label.text = text
}
}
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor),
label.widthAnchor.constraint(equalTo: widthAnchor),
])
}
}
and an example controller:
class V2_LabelHeightAnimVC: UIViewController {
let container = UIView()
var heightConstraints: [NSLayoutConstraint] = []
let testLabel = UILabel()
let testView = UIView()
var tlh: NSLayoutConstraint!
var tvh: NSLayoutConstraint!
var pcts: [[CGFloat]] = [
[25, 25, 25, 25],
[20, 10, 40, 30],
[10, 50, 30, 20],
[15, 15, 40, 30],
]
var idx: Int = 0
let infoLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
//view.backgroundColor = .systemYellow
let colors: [UIColor] = [
.init(red: 1.0, green: 0.8, blue: 0.8, alpha: 1.0),
.init(red: 0.8, green: 1.0, blue: 0.8, alpha: 1.0),
.init(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0),
.init(red: 0.9, green: 0.9, blue: 0.6, alpha: 1.0),
]
var prevView: UIView!
for i in 0..<colors.count {
let label = EmbeddedLabelView()
label.backgroundColor = colors[i]
label.text = "\(Int(pcts[0][i]))"
label.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: container.leadingAnchor),
label.trailingAnchor.constraint(equalTo: container.trailingAnchor),
label.widthAnchor.constraint(equalToConstant: 60.0),
])
if i == 0 {
label.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
} else {
label.topAnchor.constraint(equalTo: prevView.bottomAnchor).isActive = true
}
if i == colors.count - 1 {
label.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
}
prevView = label
let c = label.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.25)
c.priority = .defaultHigh
heightConstraints.append(c)
}
heightConstraints.removeLast()
NSLayoutConstraint.activate(heightConstraints)
container.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(container)
let instructionLabel = UILabel()
instructionLabel.text = "\nTap to change the percentages:"
[instructionLabel, infoLabel].forEach { v in
v.font = .monospacedSystemFont(ofSize: 18, weight: .light)
v.numberOfLines = 0
}
let vStack = UIStackView()
vStack.axis = .vertical
vStack.spacing = 12
vStack.alignment = .center
vStack.backgroundColor = .init(red: 0.90, green: 0.90, blue: 1.0, alpha: 1.0)
[instructionLabel, infoLabel].forEach { v in
vStack.addArrangedSubview(v)
}
vStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(vStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
container.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
vStack.topAnchor.constraint(equalTo: container.bottomAnchor, constant: 20.0),
vStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
vStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
vStack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
updateInfo()
}
func updateInfo() {
var s: String = "\n"
for i in 0..<pcts.count {
s += "\(pcts[i])"
if i == idx % pcts.count {
s += " <--"
}
s += "\n"
}
infoLabel.text = s
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
NSLayoutConstraint.deactivate(heightConstraints)
heightConstraints = []
idx += 1
updateInfo()
let newPcts = pcts[idx % pcts.count]
for i in 0..<newPcts.count {
let p = newPcts[i] / 100.0
let v = container.subviews[i]
if i < newPcts.count - 1 {
let c = v.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: p)
heightConstraints.append(c)
}
if let vv = v as? EmbeddedLabelView {
vv.text = "\(Int(newPcts[i]))"
}
}
NSLayoutConstraint.activate(self.heightConstraints)
UIView.animate(withDuration: 1.0, animations: {
self.view.layoutIfNeeded()
})
}
}
That looks like this:
Each tap will cycle to the next set of percentages.
Edit - because I hate answering an Obj-C question with Swift code...
Here is a similar implementation as above, with a few "enhancements."
EmbeddedLabelView class
LabelBarsView class
Values are translated into percentages of the sum, so...
if we pass [1, 1, 1, 1] each bar height will be 25%
if we pass [5, 10, 15, 20] the bar heights will be 10%, 20% 30%, 40%
EmbeddedLabelView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface EmbeddedLabelView : UIView
#property (strong, nonatomic) NSString *text;
#end
NS_ASSUME_NONNULL_END
EmbeddedLabelView.m
#import "EmbeddedLabelView.h"
#interface EmbeddedLabelView ()
{
UILabel *label;
}
#end
#implementation EmbeddedLabelView
- (instancetype)init
{
self = [super init];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
label = [UILabel new];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:label];
[NSLayoutConstraint activateConstraints:#[
// constrain all 4 sides
[label.topAnchor constraintEqualToAnchor:self.topAnchor constant:0.0],
[label.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:0.0],
[label.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:0.0],
[label.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:0.0],
]];
}
- (void)setText:(NSString *)text {
label.text = text;
_text = text;
}
#end
LabelBarsView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface LabelBarsView : UIView
#property (strong, nonatomic) NSArray <UIColor *>*colors;
#property (strong, nonatomic) NSArray <NSNumber *>*values;
#end
NS_ASSUME_NONNULL_END
LabelBarsView.m
#import "LabelBarsView.h"
#import "EmbeddedLabelView.h"
#interface LabelBarsView ()
{
NSMutableArray <NSLayoutConstraint *>*heightConstraints;
}
#end
#implementation LabelBarsView
- (instancetype)init
{
self = [super init];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
heightConstraints = [NSMutableArray new];
self.clipsToBounds = YES;
}
- (void)setColors:(NSArray<UIColor *> *)colors {
if (colors.count == self.subviews.count) {
// we're just changing the bar background colors
for (int i = 0; i < colors.count; i++) {
self.subviews[i].backgroundColor = colors[i];
}
return;
}
// we're either setting colors for the first time, or
// changing the number of bars
for (UIView *v in self.subviews) {
[v removeFromSuperview];
}
heightConstraints = [NSMutableArray new];
EmbeddedLabelView *prevView;
NSLayoutConstraint *c;
float m = 1.0 / colors.count;
for (int i = 0; i < colors.count; i++) {
EmbeddedLabelView *v = [EmbeddedLabelView new];
v.backgroundColor = colors[i];
v.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:v];
[NSLayoutConstraint activateConstraints:#[
[v.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:0.0],
[v.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:0.0],
]];
if (!prevView) {
[v.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
} else {
[v.topAnchor constraintEqualToAnchor:prevView.bottomAnchor].active = YES;
}
if (i == colors.count - 1) {
[v.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
}
prevView = v;
c = [v.heightAnchor constraintEqualToAnchor:self.heightAnchor multiplier:m];
[heightConstraints addObject:c];
}
// to avoid auto-layout complaints with fractional constraints
// we don't use Height constraint on bottom bar/view
// it will "fill" the remaining space
[heightConstraints removeLastObject];
[NSLayoutConstraint activateConstraints:heightConstraints];
_colors = colors;
}
- (void)setValues:(NSArray<NSNumber *> *)values {
if (values.count != self.subviews.count) {
// must send the same number of values as bars
return;
}
// convert values to percentages
float sum = [[values valueForKeyPath:#"#sum.self"] floatValue];
[NSLayoutConstraint deactivateConstraints:heightConstraints];
heightConstraints = [NSMutableArray new];
for (int i = 0; i < values.count; i++) {
EmbeddedLabelView *v = self.subviews[i];
CGFloat p = [values[i] floatValue];
v.text = [NSString stringWithFormat:#"%ld", (unsigned long)p];
NSLayoutConstraint *c = [v.heightAnchor constraintEqualToAnchor:self.heightAnchor multiplier:p / sum];
[heightConstraints addObject:c];
}
// to avoid auto-layout complaints with fractional constraints
// we don't use Height constraint on bottom bar/view
// it will "fill" the remaining space
[heightConstraints removeLastObject];
[NSLayoutConstraint activateConstraints:heightConstraints];
_values = values;
}
#end
LabelBarsViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface LabelBarsViewController : UIViewController
#end
NS_ASSUME_NONNULL_END
LabelBarsViewController.m
#import "LabelBarsViewController.h"
#import "LabelBarsView.h"
#interface LabelBarsViewController ()
{
LabelBarsView *barsView;
NSArray <NSArray *>*someValues;
NSInteger valIDX;
UILabel *infoLabel;
}
#end
#implementation LabelBarsViewController
- (void)viewDidLoad {
[super viewDidLoad];
// some sample values
someValues = #[
#[#1, #1, #1, #1],
#[#1, #2, #3, #4],
#[#4, #3, #2, #1],
#[#5, #10, #15, #20],
#[#20, #10, #40, #30],
#[#350, #120, #500, #280],
#[#10, #50, #30, #20],
#[#15, #15, #40, #30],
];
NSArray *colors = #[
[UIColor colorWithRed:1.0 green:0.9 blue:0.9 alpha:1.0],
[UIColor colorWithRed:0.6 green:1.0 blue:0.6 alpha:1.0],
[UIColor colorWithRed:0.8 green:0.9 blue:1.0 alpha:1.0],
[UIColor colorWithRed:1.0 green:0.9 blue:0.0 alpha:1.0],
];
barsView = [LabelBarsView new];
[barsView setColors:colors];
barsView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:barsView];
UILabel *instructionLabel = [UILabel new];
instructionLabel.font = [UIFont monospacedSystemFontOfSize:14.0 weight:UIFontWeightLight];
instructionLabel.numberOfLines = 0;
instructionLabel.textAlignment = NSTextAlignmentCenter;
instructionLabel.text = #"Tap to cycle through value sets...";
instructionLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:instructionLabel];
infoLabel = [UILabel new];
infoLabel.font = [UIFont monospacedSystemFontOfSize:14.0 weight:UIFontWeightLight];
infoLabel.numberOfLines = 0;
infoLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:infoLabel];
UILayoutGuide *g = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:#[
[barsView.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[barsView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:20.0],
[barsView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:-20.0],
[barsView.widthAnchor constraintEqualToConstant:80.0],
[instructionLabel.topAnchor constraintEqualToAnchor:g.topAnchor constant:40.0],
[instructionLabel.leadingAnchor constraintEqualToAnchor:barsView.trailingAnchor constant:20.0],
[instructionLabel.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
[infoLabel.topAnchor constraintEqualToAnchor:instructionLabel.bottomAnchor constant:20.0],
[infoLabel.leadingAnchor constraintEqualToAnchor:barsView.trailingAnchor constant:20.0],
[infoLabel.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
]];
valIDX = -1;
[self nextValues];
}
- (void) nextValues {
++valIDX;
[self updateInfo];
[barsView setValues:someValues[valIDX % someValues.count]];
}
- (void) updateInfo {
NSMutableString *s = [NSMutableString new];
for (int i = 0; i < someValues.count; i++) {
[s appendString:(i == valIDX % someValues.count ? #"--> [" : #" [")];
for (int j = 0; j < someValues[i].count; j++) {
[s appendFormat:#"%#", someValues[i][j]];
if (j < someValues[i].count - 1) {
[s appendString:#", "];
}
}
[s appendString:#"]"];
[s appendString:#"\n"];
}
infoLabel.text = s;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self nextValues];
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
}
#end
Looks like this when running - each tap cycles to the next values set:

Related

line breaks not working on UILabel in tableFooterView

I habe a tableView with a footerView. It should display a simple label.
In my ViewController, in viewDidLoad, I assign the tableFooterView like so:
let footerView = MyFooterView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 0))
tableView.tableFooterView = footerView
MyFooterView is a UIView with a single label. The label setup looks like so:
label.font = someFont
label.adjustsFontForContentSizeCategory = true
label.textColor = .black
label.numberOfLines = 0
label.text = "my super looooooong label that should break some lines but it doesn't."
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 40),
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -40),
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 20),
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20)
])
In order to get AutoLayout to work with MyFooterView, I call this method inside UIViewControllers viewDidLayoutSubviews:
func sizeFooterToFit() {
if let footerView = self.tableFooterView {
footerView.setNeedsLayout()
footerView.layoutIfNeeded()
let height = footerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = footerView.frame
frame.size.height = height
footerView.frame = frame
self.tableFooterView = footerView
}
}
Problem: The lines in the label do not break. I get the following result:
What can I do so that the label has multiple lines? AutoLayout is working thanks to the method sizeFooterToFit. The only thing is that the labels height is only as high as a single line.
HERE is the way how you can achieve it for tableHeaderView and with your case you just need to add below code in your UIViewController class
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tbl.updateHeaderViewHeight()
}
And Helper extension
extension UITableView {
func updateHeaderViewHeight() {
if let header = self.tableFooterView {
let newSize = header.systemLayoutSizeFitting(CGSize(width: self.bounds.width, height: 0))
header.frame.size.height = newSize.height
}
}
}
And remove
func sizeFooterToFit() {
if let footerView = self.tableFooterView {
footerView.setNeedsLayout()
footerView.layoutIfNeeded()
let height = footerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = footerView.frame
frame.size.height = height
footerView.frame = frame
self.tableFooterView = footerView
}
}
Above code.
And result will be:

Swift UITextView invisible with contraints

I am adding a UITextView to the screen and adding the constraints but it does not show up on the view.
class ViewController: UIViewController {
let balanceTitle: UITextView = {
let textView = UITextView();
textView.text = "hello balance";
textView.translatesAutoresizingMaskIntoConstraints = false;
textView.textColor = .red;
textView.backgroundColor = .gray;
return textView;
}()
let labelTitle: UILabel = {
let lblView = UILabel();
lblView.text = "mmsmsmsmsmsmsmsm";
lblView.textColor = .red;
lblView.translatesAutoresizingMaskIntoConstraints = false;
return lblView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//view.backgroundColor = .gray
//view.addSubview(balanceTitle)
view.addSubview(balanceTitle);
balanceTitle.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true;
balanceTitle.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true;
balanceTitle.heightAnchor.constraint(equalToConstant: 400).isActive = true;
}
}
Below is the results that I get after running the code.
You need to add width constraint
NSLayoutConstraint.activate([
balanceTitle.centerYAnchor.constraint(equalTo: view.centerYAnchor),
balanceTitle.centerXAnchor.constraint(equalTo: view.centerXAnchor),
balanceTitle.heightAnchor.constraint(equalToConstant: 400),
balanceTitle.widthAnchor.constraint(equalToConstant: 200)
])
Or like this for a proportional value with view
balanceTitle.widthAnchor.constraint(equalTo:view.widthAnchor, multiplier: 0.8)

how to change height of search bar in swift

I have been making search bars without navigation on the imageView.
The search bar height is fixed but i want to change the search bar height.
so i tried
let frame = CGRect(x: 0, y: 0, width: 100, height: 44)
searchbar.frame = frame
and
searchbar.heightAnchor.constraint(equalToConstant: 200).isActive = true
but they don't work.
I'm using this code
searchBar.isTranslucent = true
searchBar.searchBarStyle = .minimal
so like this
please help me change the search bar textfield height.
fileprivate func customizeSearchField(){
UISearchBar.appearance().setSearchFieldBackgroundImage(UIImage(), for: .normal)
searchField.backgroundColor = .white
if let searchTextField = searchField.value(forKey: "searchField") as? UITextField {
searchTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchTextField.heightAnchor.constraint(equalToConstant: 50),
searchTextField.leadingAnchor.constraint(equalTo: searchField.leadingAnchor, constant: 10),
searchTextField.trailingAnchor.constraint(equalTo: searchField.trailingAnchor, constant: -10),
searchTextField.centerYAnchor.constraint(equalTo: searchField.centerYAnchor, constant: 0)
])
searchTextField.clipsToBounds = true
searchTextField.font = GenericUtility.getOpenSansRegularFontSize(14)
searchTextField.layer.cornerRadius = 4.0
searchTextField.layer.borderWidth = 1.0
searchTextField.layer.borderColor = AppColor.primaryLightGray.cgColor
}
}
try this!
for myView in searchBars.subviews {
for mySubView in myView.subviews {
if let textField = mySubView as? UITextField {
var bounds: CGRect
bounds = textField.frame
bounds.size.height = 40 //(set your height)
textField.bounds = bounds
textField.borderStyle = UITextBorderStyle.RoundedRect
}
}
}
Try this:
searchBar.frame.size.height = 44
if you want to use with interface builder:
class MediaSearchBar: UISearchBar {
override func layoutSubviews() {
}
}
and setup it in viewDidLoad:
func setupSearchBar() {
searchBar.delegate = self
for myView in searchBar.subviews {
for mySubView in myView.subviews {
if let textField = mySubView as? UITextField {
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textField.heightAnchor.constraint(equalTo: myView.heightAnchor,
multiplier: 1.0, constant: -20.0),
textField.leadingAnchor.constraint(equalTo: myView.leadingAnchor, constant: 10.0),
textField.trailingAnchor.constraint(equalTo: myView.trailingAnchor, constant: -10.0),
textField.centerYAnchor.constraint(equalTo: myView.centerYAnchor, constant: 0.0)
])
textField.clipsToBounds = true
}
}
}
}

Add views in UIStackView programmatically

I'm trying to add views in UIStackView programmatically.
For now My code is:
UIView *view1 = [[UIView alloc]init];
view1.backgroundColor = [UIColor blackColor];
[view1 setFrame:CGRectMake(0, 0, 100, 100)];
UIView *view2 = [[UIView alloc]init];
view2.backgroundColor = [UIColor greenColor];
[view2 setFrame:CGRectMake(0, 100, 100, 100)];
[self.stack1 addArrangedSubview:view1];
[self.stack1 addArrangedSubview:view2];
When i deploy the app, there is only 1 view and it is with black colour.(view1 get the parameters for view2 too)
Stack views use intrinsic content size, so use layout constraints to define the dimensions of the views.
There is an easy way to add constraints quickly (example):
[view1.heightAnchor constraintEqualToConstant:100].active = true;
Complete Code:
- (void) setup {
//View 1
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor blueColor];
[view1.heightAnchor constraintEqualToConstant:100].active = true;
[view1.widthAnchor constraintEqualToConstant:120].active = true;
//View 2
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor greenColor];
[view2.heightAnchor constraintEqualToConstant:100].active = true;
[view2.widthAnchor constraintEqualToConstant:70].active = true;
//View 3
UIView *view3 = [[UIView alloc] init];
view3.backgroundColor = [UIColor magentaColor];
[view3.heightAnchor constraintEqualToConstant:100].active = true;
[view3.widthAnchor constraintEqualToConstant:180].active = true;
//Stack View
UIStackView *stackView = [[UIStackView alloc] init];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.distribution = UIStackViewDistributionEqualSpacing;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 30;
[stackView addArrangedSubview:view1];
[stackView addArrangedSubview:view2];
[stackView addArrangedSubview:view3];
stackView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:stackView];
//Layout for Stack View
[stackView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = true;
[stackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = true;
}
Note: This was tested on iOS 9
Swift 5.0
//Image View
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blue
imageView.heightAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.image = UIImage(named: "buttonFollowCheckGreen")
//Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.yellow
textLabel.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.text = "Hi World"
textLabel.textAlignment = .center
//Stack View
let stackView = UIStackView()
stackView.axis = NSLayoutConstraint.Axis.vertical
stackView.distribution = UIStackView.Distribution.equalSpacing
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 16.0
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
//Constraints
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
Based on #user1046037 answer.
In Swift 4.2
let redView = UIView()
redView.backgroundColor = .red
let blueView = UIView()
blueView.backgroundColor = .blue
let stackView = UIStackView(arrangedSubviews: [redView, blueView])
stackView.axis = .vertical
stackView.distribution = .fillEqually
view.addSubview(stackView)
// stackView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
// autolayout constraint
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor),
stackView.leftAnchor.constraint(equalTo: view.leftAnchor),
stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
stackView.heightAnchor.constraint(equalToConstant: 200)
])
UIStackView uses constraints internally to position its arranged subviews. Exactly what constraints are created depends on how the stack view itself is configured. By default, a stack view will create constraints that lay out its arranged subviews in a horizontal line, pinning the leading and trailing views to its own leading and trailing edges. So your code would produce a layout that looks like this:
|[view1][view2]|
The space that is allocated to each subview is determined by a number of factors including the subview's intrinsic content size and it's compression resistance and content hugging priorities. By default, UIView instances don't define an intrinsic content size. This is something that is generally provided by a subclass, such as UILabel or UIButton.
Since the content compression resistance and content hugging priorities of two new UIView instances will be the same, and neither view provides an intrinsic content size, the layout engine must make its best guess as to what size should be allocated to each view. In your case, it is assigning the first view 100% of the available space, and nothing to the second view.
If you modify your code to use UILabel instances instead, you will get better results:
UILabel *label1 = [UILabel new];
label1.text = #"Label 1";
label1.backgroundColor = [UIColor blueColor];
UILabel *label2 = [UILabel new];
label2.text = #"Label 2";
label2.backgroundColor = [UIColor greenColor];
[self.stack1 addArrangedSubview:label1];
[self.stack1 addArrangedSubview:label2];
Note that it is not necessary to explictly create any constraints yourself. This is the main benefit of using UIStackView - it hides the (often ugly) details of constraint management from the developer.
You have to set you distribution type.
In your code, juste add:
self.stack1.distribution = UIStackViewDistributionFillEqually;
Or you can set the distribution directly in your interface builder.
For example:
Hope that helps ;)
Lapinou.
Following two lines fixed my issue
view.heightAnchor.constraintEqualToConstant(50).active = true;
view.widthAnchor.constraintEqualToConstant(350).active = true;
Swift version -
var DynamicView=UIView(frame: CGRectMake(100, 200, 100, 100))
DynamicView.backgroundColor=UIColor.greenColor()
DynamicView.layer.cornerRadius=25
DynamicView.layer.borderWidth=2
self.view.addSubview(DynamicView)
DynamicView.heightAnchor.constraintEqualToConstant(50).active = true;
DynamicView.widthAnchor.constraintEqualToConstant(350).active = true;
var DynamicView2=UIView(frame: CGRectMake(100, 310, 100, 100))
DynamicView2.backgroundColor=UIColor.greenColor()
DynamicView2.layer.cornerRadius=25
DynamicView2.layer.borderWidth=2
self.view.addSubview(DynamicView2)
DynamicView2.heightAnchor.constraintEqualToConstant(50).active = true;
DynamicView2.widthAnchor.constraintEqualToConstant(350).active = true;
var DynamicView3:UIView=UIView(frame: CGRectMake(10, 420, 355, 100))
DynamicView3.backgroundColor=UIColor.greenColor()
DynamicView3.layer.cornerRadius=25
DynamicView3.layer.borderWidth=2
self.view.addSubview(DynamicView3)
let yourLabel:UILabel = UILabel(frame: CGRectMake(110, 10, 200, 20))
yourLabel.textColor = UIColor.whiteColor()
//yourLabel.backgroundColor = UIColor.blackColor()
yourLabel.text = "mylabel text"
DynamicView3.addSubview(yourLabel)
DynamicView3.heightAnchor.constraintEqualToConstant(50).active = true;
DynamicView3.widthAnchor.constraintEqualToConstant(350).active = true;
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.Vertical
stackView.distribution = UIStackViewDistribution.EqualSpacing
stackView.alignment = UIStackViewAlignment.Center
stackView.spacing = 30
stackView.addArrangedSubview(DynamicView)
stackView.addArrangedSubview(DynamicView2)
stackView.addArrangedSubview(DynamicView3)
stackView.translatesAutoresizingMaskIntoConstraints = false;
self.view.addSubview(stackView)
//Constraints
stackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
stackView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true
For the accepted answer when you try to hide any view inside stack view, the constraint works not correct.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x618000086e50 UIView:0x7fc11c4051c0.height == 120 (active)>",
"<NSLayoutConstraint:0x610000084fb0 'UISV-hiding' UIView:0x7fc11c4051c0.height == 0 (active)>"
)
Reason is when hide the view in stackView it will set the height to 0 to animate it.
Solution change the constraint priority as below.
import UIKit
class ViewController: UIViewController {
let stackView = UIStackView()
let a = UIView()
let b = UIView()
override func viewDidLoad() {
super.viewDidLoad()
a.backgroundColor = UIColor.red
a.widthAnchor.constraint(equalToConstant: 200).isActive = true
let aHeight = a.heightAnchor.constraint(equalToConstant: 120)
aHeight.isActive = true
aHeight.priority = 999
let bHeight = b.heightAnchor.constraint(equalToConstant: 120)
bHeight.isActive = true
bHeight.priority = 999
b.backgroundColor = UIColor.green
b.widthAnchor.constraint(equalToConstant: 200).isActive = true
view.addSubview(stackView)
stackView.backgroundColor = UIColor.blue
stackView.addArrangedSubview(a)
stackView.addArrangedSubview(b)
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.translatesAutoresizingMaskIntoConstraints = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Just add a button in xib file or storyboard and add connect this action.
#IBAction func test(_ sender: Any) {
a.isHidden = !a.isHidden
}
}
//Image View
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blueColor()
imageView.heightAnchor.constraintEqualToConstant(120.0).active = true
imageView.widthAnchor.constraintEqualToConstant(120.0).active = true
imageView.image = UIImage(named: "buttonFollowCheckGreen")
//Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.greenColor()
textLabel.widthAnchor.constraintEqualToConstant(self.view.frame.width).active = true
textLabel.heightAnchor.constraintEqualToConstant(20.0).active = true
textLabel.text = "Hi World"
textLabel.textAlignment = .Center
//Third View
let thirdView = UIImageView()
thirdView.backgroundColor = UIColor.magentaColor()
thirdView.heightAnchor.constraintEqualToConstant(120.0).active = true
thirdView.widthAnchor.constraintEqualToConstant(120.0).active = true
thirdView.image = UIImage(named: "buttonFollowMagenta")
//Stack View
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.Vertical
stackView.distribution = UIStackViewDistribution.EqualSpacing
stackView.alignment = UIStackViewAlignment.Center
stackView.spacing = 16.0
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textLabel)
stackView.addArrangedSubview(thirdView)
stackView.translatesAutoresizingMaskIntoConstraints = false;
self.view.addSubview(stackView)
//Constraints
stackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
stackView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true
Improved on the answer by #Oleg Popov
Swift 5 version of Oleg Popov's answer, which is based on user1046037's answer
//Image View
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blue
imageView.heightAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.image = UIImage(named: "buttonFollowCheckGreen")
//Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.yellow
textLabel.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.text = "Hi World"
textLabel.textAlignment = .center
//Stack View
let stackView = UIStackView()
stackView.axis = NSLayoutConstraint.Axis.vertical
stackView.distribution = UIStackView.Distribution.equalSpacing
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 16.0
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
//Constraints
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
In my case the thing that was messing with I would expect was that I was missing this line:
stackView.translatesAutoresizingMaskIntoConstraints = false;
After that no need to set constraints to my arranged subviews whatsoever, the stackview is taking care of that.
If you have many UIViews to add, you can use the following extension where you pass an array of UIViews where it will add them into the UIStackView by order
extension UIStackView {
func addArrangedSubviews(_ subviews: [UIView]) {
subviews.forEach{ self.addArrangedSubview($0) }
}
}
If you have want to add UIView at specific position, use this
yourStackView.insertArrangedSubview(yourView, at: index)
I just came across very similar problem. Just like mentioned before the stack view's dimensions depend one intrinsic content size of the arranged subviews. Here is my solution in Swift 2.x and following view structure:
view - UIView
customView - CustomView:UIView
stackView - UISTackView
arranged subviews - custom UIView subclasses
//: [Previous](#previous)
import Foundation
import UIKit
import XCPlayground
/**Container for stack view*/
class CustomView:UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(){
super.init(frame: CGRectZero)
}
}
/**Custom Subclass*/
class CustomDrawing:UIView{
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
// self.backgroundColor = UIColor.clearColor()
print("setup \(frame)")
}
override func drawRect(rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
CGContextMoveToPoint(ctx, 0, 0)
CGContextAddLineToPoint(ctx, CGRectGetWidth(bounds), CGRectGetHeight(bounds))
CGContextStrokePath(ctx)
print("DrawRect")
}
}
//: [Next](#next)
let stackView = UIStackView()
stackView.distribution = .FillProportionally
stackView.alignment = .Center
stackView.axis = .Horizontal
stackView.spacing = 10
//container view
let view = UIView(frame: CGRectMake(0,0,320,640))
view.backgroundColor = UIColor.darkGrayColor()
//now custom view
let customView = CustomView()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.widthAnchor.constraintEqualToConstant(220).active = true
customView.heightAnchor.constraintEqualToConstant(60).active = true
customView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
customView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
customView.backgroundColor = UIColor.lightGrayColor()
//add a stack view
customView.addSubview(stackView)
stackView.centerXAnchor.constraintEqualToAnchor(customView.centerXAnchor).active = true
stackView.centerYAnchor.constraintEqualToAnchor(customView.centerYAnchor).active = true
stackView.translatesAutoresizingMaskIntoConstraints = false
let c1 = CustomDrawing()
c1.translatesAutoresizingMaskIntoConstraints = false
c1.backgroundColor = UIColor.redColor()
c1.widthAnchor.constraintEqualToConstant(30).active = true
c1.heightAnchor.constraintEqualToConstant(30).active = true
let c2 = CustomDrawing()
c2.translatesAutoresizingMaskIntoConstraints = false
c2.backgroundColor = UIColor.blueColor()
c2.widthAnchor.constraintEqualToConstant(30).active = true
c2.heightAnchor.constraintEqualToConstant(30).active = true
stackView.addArrangedSubview(c1)
stackView.addArrangedSubview(c2)
XCPlaygroundPage.currentPage.liveView = view
Instead of coding all the constrains you could use a subclass that handles .intrinsicContentSize of UIView class in a simpler way.
This solution improves also Interface Builder a little in a way to support with "intrinsicWidth" and "intrinsicHeight" of views. While you could extend UIView's and have those properties available on all UIViews in IB its cleaner to subclass.
// IntrinsicView.h
#import UIKit
IB_DESIGNABLE
#interface IntrinsicView : UIView
-(instancetype)initWithFrame:(CGRect)rect;
#property IBInspectable CGSize intrinsic;
#end
// IntrinsicView.m
#import "IntrinsicView.h"
#implementation IntrinsicView {
CGSize _intrinsic;
}
- (instancetype)initWithFrame:(CGRect)frame {
_intrinsic = frame.size;
if ( !(self = [super initWithFrame:frame]) ) return nil;
// your stuff here..
return self;
}
-(CGSize)intrinsicContentSize {
return _intrinsic;
}
-(void)prepareForInterfaceBuilder {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _intrinsic.width,_intrinsic.height);
}
#end
Which means you can just allocate those IntrinsicView's and the self.frame.size is taken as intrinsicContentSize. That way it does not disturb the normal layout and you dont need to set constraint relations that don't even apply in full with UIStackViews
#import "IntrinsicView.h"
- (void)viewDidLoad {
[super viewDidLoad];
UIStackView *column = [[UIStackView alloc] initWithFrame:self.view.frame];
column.spacing = 2;
column.alignment = UIStackViewAlignmentFill;
column.axis = UILayoutConstraintAxisVertical; //Up-Down
column.distribution = UIStackViewDistributionFillEqually;
for (int row=0; row<5; row++) {
//..frame:(CGRect) defines here proportions and
//relation to axis of StackView
IntrinsicView *intrinsicView = [[IntrinsicView alloc] initWithFrame:CGRectMake(0, 0, 30.0, 30.0)];
[column addArrangedSubview:intrinsicView];
}
[self.view addSubview:column];
}
now you can go crazy with UIStackView's
or in swift + encoding, decoding, IB support, Objective-C support
#IBDesignable #objc class IntrinsicView : UIView {
#IBInspectable var intrinsic : CGSize
#objc override init(frame: CGRect) {
intrinsic = frame.size
super.init(frame: frame)
}
required init?(coder: NSCoder) {
intrinsic = coder.decodeCGSize(forKey: "intrinsic")
super.init(coder: coder)
}
override func encode(with coder: NSCoder) {
coder.encode(intrinsic, forKey: "intrinsic")
super.encode(with: coder)
}
override var intrinsicContentSize: CGSize {
return intrinsic
}
override func prepareForInterfaceBuilder() {
frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: intrinsic.width, height: intrinsic.height)
}
}
It is really not recommended to set a height constraint... If you can, never, never, never set a height! You need to check all the constraints of the views inside your UIStackView and be sure that there is constraints for bottom, top, leading and trailing. Someone said to me: it is like a guy pushing on walls. If he don't push on 4 sides, one of the wall will fall on him.
func configureHorizontalView(){
containerView.addSubview(horizontalStackView)
_ = horizontalStackView.anchor(top: secondCurrencyTextField.bottomAnchor,
left: containerView.leftAnchor,
bottom: nil,
right: containerView.rightAnchor,
topConstant: 40,
leftConstant: 30,
bottomConstant: 0,
rightConstant: 30,
widthConstant: 0,
heightConstant: 65)
}
func configureFirstDropDownlabel(){
//add a view to stackView with addArrangedSubview()
horizontalStackView.addArrangedSubview(firstDropDownlabel)
_ = firstDropDownlabel.anchor(top: horizontalStackView.bottomAnchor,
left: horizontalStackView.leftAnchor,
bottom: nil, right: nil,
topConstant: 40,
leftConstant: 30,
bottomConstant: 0,
rightConstant: 0,
widthConstant: 0,
heightConstant: 0)
firstDropDownlabel.widthAnchor.constraint(equalToConstant: 130).isActive = true
firstDropDownlabel.heightAnchor.constraint(equalToConstant: 65).isActive = true
}
Try below code:
UIView *view1 = [[UIView alloc]init];
view1.backgroundColor = [UIColor blackColor];
[view1 setFrame:CGRectMake(0, 0, 50, 50)];
UIView *view2 = [[UIView alloc]init];
view2.backgroundColor = [UIColor greenColor];
[view2 setFrame:CGRectMake(0, 100, 100, 100)];
NSArray *subView = [NSArray arrayWithObjects:view1,view2, nil];
[self.stack1 initWithArrangedSubviews:subView];

How to display activity indicator in center of UIAlertController?

I currently have a UIAlertController being displayed on the screen. The view of the alert should only display 2 elements, a title and a UIActivityIndicatorView in the center of the alert. Below is the function that displays the alert and its elements.
func displaySignUpPendingAlert() -> UIAlertController {
//Create the UIAlertController
let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
//Create the activity indicator to display in it.
let indicator = UIActivityIndicatorView(frame: CGRectMake(pending.view.frame.width / 2.0, pending.view.frame.height / 2.0, 20.0, 20.0))
indicator.center = CGPointMake(pending.view.frame.width / 2.0, pending.view.frame.height / 2.0)
//Add the activity indicator to the alert's view
pending.view.addSubview(indicator)
//Start animating
indicator.startAnimating()
self.presentViewController(pending, animated: true, completion: nil)
return pending
}
However, the activity indicator doesn't display in the center of the view, in fact it displays in the bottom right of the screen, far off of the view. What is the reason for this?
EDIT: I understand that I can hardcode numbers for the indicator's position, but I want the alert to work on multiple devices with multiple screen sizes and orientations.
Be sure to set the frame property when you're creating a view.
func displaySignUpPendingAlert() -> UIAlertController {
//create an alert controller
let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]
//add the activity indicator as a subview of the alert controller's view
pending.view.addSubview(indicator)
indicator.isUserInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
self.presentViewController(pending, animated: true, completion: nil)
return pending
}
To #62Shark:
let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
let indicator = UIActivityIndicatorView()
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
pending.view.addSubview(indicator)
let views = ["pending" : pending.view, "indicator" : indicator]
var constraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[indicator]-(-50)-|", options: nil, metrics: nil, views: views)
constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[indicator]|", options: nil, metrics: nil, views: views)
pending.view.addConstraints(constraints)
indicator.userInteractionEnabled = false
indicator.startAnimating()
self.presentViewController(pending, animated: true, completion: nil)
I converted the answer to Objective C, if anyone is interested:
UIAlertController *pending = [UIAlertController alertControllerWithTitle:nil
message:#"Please wait...\n\n"
preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView* indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.color = [UIColor blackColor];
indicator.translatesAutoresizingMaskIntoConstraints=NO;
[pending.view addSubview:indicator];
NSDictionary * views = #{#"pending" : pending.view, #"indicator" : indicator};
NSArray * constraintsVertical = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[indicator]-(20)-|" options:0 metrics:nil views:views];
NSArray * constraintsHorizontal = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[indicator]|" options:0 metrics:nil views:views];
NSArray * constraints = [constraintsVertical arrayByAddingObjectsFromArray:constraintsHorizontal];
[pending.view addConstraints:constraints];
[indicator setUserInteractionEnabled:NO];
[indicator startAnimating];
[self presentViewController:pending animated:YES completion:nil];
Cheers
tl;dr
All the other answers are off :) See documentation:
Important
The UIAlertController class is intended to be used as-is and does not
support subclassing. The view hierarchy for this class is private and
must not be modified.
Problem
The problem is not the UIAlertController. This is a very simple UI, a stackview or two depending if you want the UIActivityIndicatorView left to the title label or under the title. The presentation animation is what we want.
The code below is based on the WWDC session A Look Inside Presentation Controllers.
Swift
Recreate Presentation Controller:
class LOActivityAlertControllerPresentationController: UIPresentationController {
var dimmerView: UIView!
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
self.dimmerView = UIView()
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
dimmerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
dimmerView.backgroundColor = UIColor.init(white: 0, alpha: 0.4)
guard let presentedView = self.presentedView else { return }
presentedView.layer.cornerRadius = 8.0
let centerXMotionEffect: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
centerXMotionEffect.minimumRelativeValue = -10.0
centerXMotionEffect.maximumRelativeValue = 10.0
let centerYMotionEffect: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
centerYMotionEffect.minimumRelativeValue = -10.0
centerYMotionEffect.maximumRelativeValue = 10.0
let group: UIMotionEffectGroup = UIMotionEffectGroup()
group.motionEffects = [centerXMotionEffect, centerYMotionEffect]
presentedView.addMotionEffect(group)
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = self.containerView, let presentedView = self.presentedView else { return .zero }
let size = presentedView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
var frame = CGRect.zero
frame.origin = CGPoint(x: containerView.frame.midX - (size.width / 2.0), y: containerView.frame.midY - (size.height / 2.0))
frame.size = size
return frame
}
override func presentationTransitionWillBegin() {
guard let containerView: UIView = self.containerView, let presentedView: UIView = self.presentedView, let dimmerView = self.dimmerView else { return }
let presentingViewController: UIViewController = self.presentingViewController
dimmerView.alpha = 0.0
dimmerView.frame = containerView.bounds
containerView.insertSubview(dimmerView, at: 0)
presentedView.center = containerView.center
guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return }
transitionCoordinator.animate(
alongsideTransition: { _ in
dimmerView.alpha = 1.0
},
completion: nil
)
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
guard let containerView: UIView = self.containerView, let presentedView: UIView = self.presentedView, let dimmerView = self.dimmerView else { return }
dimmerView.frame = containerView.bounds
presentedView.frame = self.frameOfPresentedViewInContainerView
}
override func dismissalTransitionWillBegin() {
guard let dimmerView = self.dimmerView, let transitionCoordinator = self.presentingViewController.transitionCoordinator else { return }
transitionCoordinator.animate(
alongsideTransition: { _ in
dimmerView.alpha = 0.0
},
completion: nil
)
}
}
Animated Transitioning:
class LOActivityAlertControllerAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
var presentation: Bool
init(presentation: Bool) {
self.presentation = presentation
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from), let toView = transitionContext.view(forKey: .to) else { return }
if self.presentation {
containerView.addSubview(toView)
toView.transform = CGAffineTransform(scaleX: 1.6, y: 1.6)
toView.alpha = 0.0
UIView.animate(
withDuration: 0.2,
animations: {
toView.alpha = 1.0
toView.transform = .identity
},
completion: { finished in
transitionContext.completeTransition(true)
}
)
} else {
UIView.animate(
withDuration: 0.2,
animations: {
fromView.alpha = 0.0
},
completion: { finished in
fromView.removeFromSuperview()
transitionContext.completeTransition(true)
}
)
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.2
}
}
Sample UIViewController subclass, season to taste with XIB:
class LOActivityAlertController: UIViewController, UIViewControllerTransitioningDelegate {
var activityIndicatorView: UIActivityIndicatorView!
var titleLabel: UILabel!
var messageLabel: UILabel!
var alertTitle: String
var alertMessage: String
init(title: String, message: String) {
self.alertTitle = title
self.alertMessage = message
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("Not implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.transitioningDelegate = self
self.modalPresentationStyle = .custom
self.titleLabel = UILabel()
self.messageLabel = UILabel()
self.titleLabel.text = self.alertTitle
self.messageLabel.text = self.alertMessage
self.activityIndicatorView = UIActivityIndicatorView(style: .medium)
let currentFrame = self.view.frame
let alertFrame = CGRect(x: 0, y: 0, width: currentFrame.width / 2.0, height: currentFrame.height / 2.0)
let stackView = UIStackView(frame: alertFrame)
stackView.backgroundColor = .gray
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .fillProportionally
stackView.addArrangedSubview(self.titleLabel)
stackView.addArrangedSubview(self.messageLabel)
stackView.addArrangedSubview(self.activityIndicatorView)
self.activityIndicatorView.startAnimating()
self.view.addSubview(stackView)
}
override func viewDidAppear(_ animated: Bool) {
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let presentationController = LOActivityAlertControllerPresentationController(presentedViewController: presented, presenting: presenting)
return presentationController
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitioning = LOActivityAlertControllerAnimatedTransitioning(presentation: true)
return transitioning
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitioning = LOActivityAlertControllerAnimatedTransitioning(presentation: false)
return transitioning
}
}
Credits for swift version: #riciloma
Objective-C
Recreate Presentation Controller:
#interface LOActivityAlertControllerPresentationController : UIPresentationController
#end
#interface LOActivityAlertControllerPresentationController ()
#property (nonatomic) UIView *dimmerView;
#end
#implementation LOActivityAlertControllerPresentationController
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController
{
self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
if (self)
{
_dimmerView = [[UIView alloc] init];
_dimmerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dimmerView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4];
UIView *presentedView = [self presentedView];
presentedView.layer.cornerRadius = 8.0;
UIInterpolatingMotionEffect *centerXMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
centerXMotionEffect.minimumRelativeValue = #(-10.0);
centerXMotionEffect.maximumRelativeValue = #(10.0);
UIInterpolatingMotionEffect *centerYMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
centerYMotionEffect.minimumRelativeValue = #(-10.0);
centerYMotionEffect.maximumRelativeValue = #(10.0);
UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
group.motionEffects = [NSArray arrayWithObjects:centerXMotionEffect, centerYMotionEffect, nil];
[presentedView addMotionEffect:group];
}
return self;
}
- (CGRect)frameOfPresentedViewInContainerView
{
UIView *containerView = [self containerView];
UIView *presentedView = [self presentedView];
CGSize size = [presentedView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
CGRect frame = CGRectZero;
frame.origin = CGPointMake(CGRectGetMidX([containerView frame]) - (size.width / 2.0),
CGRectGetMidY([containerView frame]) - (size.height / 2.0));
frame.size = size;
return frame;
}
- (void)presentationTransitionWillBegin
{
UIViewController *presentingViewController = [self presentingViewController];
UIView *containerView = [self containerView];
UIView *presentedView = [self presentedView];
UIView *dimmerView = [self dimmerView];
dimmerView.alpha = 0.0;
dimmerView.frame = [containerView bounds];
[containerView insertSubview:dimmerView atIndex:0];
presentedView.center = [containerView center];
[[presentingViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
dimmerView.alpha = 1.0;
} completion:NULL];
}
- (void)containerViewWillLayoutSubviews
{
[super containerViewWillLayoutSubviews];
UIView *containerView = [self containerView];
UIView *presentedView = [self presentedView];
UIView *dimmerView = [self dimmerView];
dimmerView.frame = [containerView bounds];
presentedView.frame = [self frameOfPresentedViewInContainerView];
}
- (void)dismissalTransitionWillBegin
{
UIViewController *presentingViewController = [self presentingViewController];
UIView *dimmerView = [self dimmerView];
[[presentingViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
dimmerView.alpha = 0.0;
} completion:NULL];
}
#end
Animated Transitioning:
#interface LOActivityAlertControllerAnimatedTransitioning : NSObject <UIViewControllerAnimatedTransitioning>
#property (getter=isPresentation) BOOL presentation;
#end
#implementation LOActivityAlertControllerAnimatedTransitioning
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
if (_presentation)
{
[containerView addSubview:toView];
toView.transform = CGAffineTransformMakeScale(1.6, 1.6);
toView.alpha = 0.0;
[UIView animateWithDuration:0.2 animations:^{
toView.alpha = 1.0;
toView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
else
{
[UIView animateWithDuration:0.2 animations:^{
fromView.alpha = 0.0;
} completion:^(BOOL finished) {
[fromView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
}
- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.2;
}
#end
Sample UIViewController subclass, season to taste with XIB:
#interface LOActivityAlertController : UIViewController <UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) IBOutlet UIActivityIndicatorView *activityIndicatorView;
#property (nonatomic, strong) IBOutlet UILabel *titleLabel;
#end
#implementation LOActivityAlertController
#dynamic title;
+ (instancetype)alertControllerWithTitle:(NSString *)title
{
LOActivityAlertController *alert = [LOActivityAlertController new];
alert.title = title;
return alert;
}
- (instancetype)init
{
self = [super init];
if (self)
{
self.transitioningDelegate = self;
self.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.titleLabel.text = self.title;
}
#pragma mark Properties
- (void)setTitle:(NSString *)title
{
[super setTitle:title];
self.titleLabel.text = title;
}
#pragma mark UIViewControllerTransitioningDelegate
- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented
presentingViewController:(UIViewController *)presenting
sourceViewController:(UIViewController *)source
{
LOActivityAlertControllerPresentationController *myPresentation = nil;
myPresentation = [[LOActivityAlertControllerPresentationController alloc]
initWithPresentedViewController:presented presentingViewController:presenting];
return myPresentation;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
{
LOActivityAlertControllerAnimatedTransitioning *transitioning = [LOActivityAlertControllerAnimatedTransitioning new];
transitioning.presentation = YES;
return transitioning;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
LOActivityAlertControllerAnimatedTransitioning *transitioning = [LOActivityAlertControllerAnimatedTransitioning new];
return transitioning;
}
#end
Screen Recording
Bug Reporter
rdar://37433306: Make UIAlertController presentation controller and transitioning delegate public API to enable reuse.
Swift 5.0 solution
let alert = UIAlertController(title: "Sender ...", message: nil, preferredStyle: .alert)
let activityIndicator = UIActivityIndicatorView(style: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false
activityIndicator.startAnimating()
alert.view.addSubview(activityIndicator)
alert.view.heightAnchor.constraint(equalToConstant: 95).isActive = true
activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor, constant: 0).isActive = true
activityIndicator.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -20).isActive = true
present(alert, animated: true)
I have to implement NSLayoutConstraints to put the UIActivityIndicatorView on the center of the UIAlertController
For Swift:
let loadingAlertController: UIAlertController = UIAlertController(title: "Loading", message: nil, preferredStyle: .alert)
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
loadingAlertController.view.addSubview(activityIndicator)
let xConstraint: NSLayoutConstraint = NSLayoutConstraint(item: activityIndicator, attribute: .centerX, relatedBy: .equal, toItem: loadingAlertController.view, attribute: .centerX, multiplier: 1, constant: 0)
let yConstraint: NSLayoutConstraint = NSLayoutConstraint(item: activityIndicator, attribute: .centerY, relatedBy: .equal, toItem: loadingAlertController.view, attribute: .centerY, multiplier: 1.4, constant: 0)
NSLayoutConstraint.activate([ xConstraint, yConstraint])
activityIndicator.isUserInteractionEnabled = false
activityIndicator.startAnimating()
let height: NSLayoutConstraint = NSLayoutConstraint(item: loadingAlertController.view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 80)
loadingAlertController.view.addConstraint(height)
self.present(loadingAlertController, animated: true, completion: nil)
Result:
For those like me who prefer UIActivityIndicatorView aligned at the left of the UIAlertController.title, this is my solution in Swift working for all devices:
let alert = UIAlertController(title: NSLocalizedString("Authenticating...", comment: "Authenticating"), message: nil, preferredStyle: .Alert);
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
activityIndicator.frame = activityIndicator.frame.rectByOffsetting(dx: 8, dy: (alert.view.bounds.height - activityIndicator.frame.height)/2);
activityIndicator.autoresizingMask = .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin
activityIndicator.color = themeManager().currentTheme.navigationBarTintColor;
activityIndicator.startAnimating();
alert.view.addSubview(activityIndicator);
self.presentViewController(progressAlert, animated: true, completion: nil);
However, to align the UIActivityIndicatorView in the view center you can change as follows:
activityIndicator.center = CGPoint(x: (alert.view.bounds.width)/2, y: (alert.view.bounds.height)/2)
activityIndicator.autoresizingMask = .FlexibleLeftMargin | .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin
Apple does not encourage directly subclassing UIAlertController so I made a class that displays UIAlertController with centered UIActivityIndicator and handles the cancel condition with a class protocol.
import Foundation
import UIKit
protocol BusyAlertDelegate {
func didCancelBusyAlert()
}
class BusyAlert {
var busyAlertController: UIAlertController?
var presentingViewController: UIViewController?
var activityIndicator: UIActivityIndicatorView?
var delegate:BusyAlertDelegate?
init (title:String, message:String, presentingViewController: UIViewController) {
busyAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
busyAlertController!.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel Button"), style: UIAlertActionStyle.Cancel, handler:{(alert: UIAlertAction!) in
delegate?.didCancelBusyAlert()
}))
self.presentingViewController = presentingViewController
activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
busyAlertController!.view.addSubview(activityIndicator!)
}
func display() {
dispatch_async(dispatch_get_main_queue(), {
self.presentingViewController!.presentViewController(self.busyAlertController!, animated: true, completion: {
self.activityIndicator!.translatesAutoresizingMaskIntoConstraints = false
self.busyAlertController!.view.addConstraint(NSLayoutConstraint(item: self.activityIndicator!, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.busyAlertController!.view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
self.busyAlertController!.view.addConstraint(NSLayoutConstraint(item: self.activityIndicator!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.busyAlertController!.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
self.activityIndicator!.startAnimating()
})
})
}
func dismiss() {
dispatch_async(dispatch_get_main_queue(), {
self.busyAlertController?.dismissViewControllerAnimated(true, completion: nil)
})
}
}
I recommend using lazy var to initialize the class.
lazy var busyAlertController: BusyAlert = {
let busyAlert = BusyAlert(title: "Lengthy Task", message: "Please wait...", presentingViewController: self)
busyAlert.delegate = self
return busyAlert
}()
Here is a link to sample code: https://github.com/cgilleeny/BusyAlertExample.git
It's this simple.
fully tested ...
extension UIViewController {
func verySimpleSpinner() -> UIAlertController {
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
alert.view.addSubview(spinner)
spinner.bindEdgesToSuperview()
present(alert, animated: true, completion: nil)
return alert
}
}
It's impossible to write iOS apps unless you have a simple .bindEdgesToSuperview() call -
extension UIView {
func bindEdgesToSuperview() {
guard let s = superview else { preconditionFailure("flop") }
translatesAutoresizingMaskIntoConstraints = false
leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
topAnchor.constraint(equalTo: s.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
}
}
If you want text
If you do also want text, use the excellent code from #magnuskahr. Modernized:
fully tested ...
extension UIView {
func verySimpleSpinner() -> UIAlertController {
let alert = UIAlertController(title: "", message: "Connecting...", preferredStyle: .alert)
let spinner = UIActivityIndicatorView(style: .medium)
alert.view.addSubview(spinner)
alert.view.heightAnchor.constraint(equalToConstant: 95).isActive = true
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor, constant: 0).isActive = true
spinner.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -20).isActive = true
spinner.startAnimating()
present(alert, animated: true, completion: nil)
return alert
}
}
How to use
In any view controller:
let spinny = verySimpleSpinner()
when the connection/etc has finished:
spinny.dismiss(animated: true)
In swift:
activityIndicator.center = self.view.center
If you have a tool bar or a navController you might want to shift the point but otherwise, center is center...
If you still have issues, perhaps this tutorial would help. If you are trying to center it in a table view controller, this answer might help.
If you want a ActivityIndicatorView only alert then try this.
func presentLoader() {
let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert)
let activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false
activityIndicator.color = .blue
activityIndicator.startAnimating()
alert.view.addSubview(activityIndicator)
NSLayoutConstraint.activate([
alert.view.heightAnchor.constraint(equalToConstant: 95),
alert.view.widthAnchor.constraint(equalToConstant: 95),
activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor)
])
present(alert, animated: true)
}
Result:
Converted #petesalt's answer to Swift 3:
let pending = UIAlertController(title: "Saving, please wait...", message: nil, preferredStyle: .alert)
let indicator = UIActivityIndicatorView()
indicator.translatesAutoresizingMaskIntoConstraints = false
pending.view.addSubview(indicator)
let views = ["pending" : pending.view, "indicator" : indicator]
var constraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[indicator]-(-50)-|", options: NSLayoutFormatOptions.alignAllCenterY, metrics: nil, views: views)
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[indicator]|", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: views)
pending.view.addConstraints(constraints)
indicator.isUserInteractionEnabled = false
indicator.startAnimating()
self.present(pending, animated: true, completion: nil)
How about this way for Swift 3 and higher:
func showActivityIndiactorViewController(title: String) -> UIAlertController {
let pending = UIAlertController(title: "", message: nil, preferredStyle: .alert)
let heightConstraint:NSLayoutConstraint = NSLayoutConstraint(item: pending.view, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: self.view.frame.height * 0.10)
pending.view.addConstraint(heightConstraint)
let label = UILabel()
label.text = title
label.textColor = UIColor.black
label.sizeToFit()
let space = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 8))
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.isUserInteractionEnabled = false
indicator.startAnimating()
let width = Int(label.frame.size.width + indicator.frame.size.width + space.frame.size.width)
let view = UIStackView(arrangedSubviews: [indicator, space, label])
view.axis = .horizontal
view.frame = CGRect(x: 20, y: 0, width: width, height: Int(heightConstraint.constant))
pending.view.addSubview(view)
let widthConstraint:NSLayoutConstraint = NSLayoutConstraint(item: pending.view, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.greaterThanOrEqual, toItem: view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: CGFloat(width))
pending.view.addConstraint(widthConstraint)
self.present(pending, animated: true, completion: nil)
return pending
}
Well try this code.
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
message:#"Creating new user\n\n\n"
preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView *loader = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
loader.center = CGPointMake(130.5, 65.5);
loader.color = [UIColor blackColor];
[loader startAnimating];
[alert.view loader];
[self presentViewController:alert animated:NO completion:nil];
Try this:
activityView.center = CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height / 2.0)
Also you will need to check for landscape mode and reverse width and height.
if(landscapeMode)activityView.center = CGPointMake(self.view.bounds.size.height/2.0, self.view.bounds.size.width / 2.0)
Maybe you can get the alert view position?
alert.view.frame.origin.x
alert.view.frame.origin.y
and use that to place your activity view dynamically ie with the variables?
Of course you might also want to get the size divide by 2 and add that so that its centred as well.
alert.view.frame.size.height
alert.view.frame.size.width
I had the same problem and using frame positioning didn't work for me.
Yimin Lin's answer was very close for me, but I just wanted to present an alternative using constraints in non-visual format:
//...
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
alert.view.addSubview(indicator)
alert.view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: alert.view, attribute: attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
alert.view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: alert.view, attribute: attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
//...

Resources