Vertical Progress bar in iOS with Autolayout - ios

I am trying to make a vertical progress bar in iOS that can be placed as a UIView in interface builder, and given a progress bar view class. Here are my class files:
ProgressBar.h:
#interface ProgressBar : UIView
#property (nonatomic, retain) IBOutlet UIView *barView
-(void)setBarValue:(float)value;
#end
ProgressBar.m:
-(id)initWithCoder:(NSCoder *)aDecoder {
id s = [super initWithCoder:aDecoder];
if (s) {
self.backgroundColor = [UIColor clearColor];
}
return s;
}
-(void)setBarValue:(float)val {
[self.barView setBackgroudnColor:TURQUOISE];
CGRect frame = self.barView.frame;
frame.size.height = self.barview.frame.size.height * val;
[self.barView setFrame:frame];
}
The only constraint I have on the 'barView' inside the ProgressBar element is 'align bottom edges'. The setFrame method never seems to update the bar, and even at full height, the inner self.barView isn't the same height as the ProgressBar view. The properties are all correctly linked in the Storyboard.
Any ideas?

The key issue is that, if using autolayout, you should add your constraints for this subview in IB, too. (I assume you already added constraints for the ProgressView.) And you should not change the frame of barView, but rather change its constraints and then call setNeedsLayout. The easiest way will be to add leading/trailing/top constraints of zero and then create a final height constraint. You can then add an IBOutlet for that height constraint. You can then programmatically set the constant of that constraint (and if you're going to use a multiplicative factor, it should be the product of this factor and the overall progress view, not a factor of the bar, itself), and then call setNeedsLayout.

Related

Layout not changing when parent (UIView) size is changed

I have a UIView and inside this UIView I have another UIView, lets say parent and child UIView. I have set height and width of parent UIView to 400 in storyboard and set child view constraint to take 8px margin from top, left right and bottom from its superview.
But When I change size of parent view to 200, size of child view remain same. I have tried this in both viewdidload and viewdidappear
CGRect frm = self.mainTimerView.frame;
frm.size.width = size;
frm.size.height = size;
self.mainTimerView.frame = frm;
when I change parent view to 200 child should set it self to 200-16 height and width according to constraints.
You should not mix using uiconstraint with using frame.If you want to change the size when using uiconstraint, you should make the outlet of the constraint, and then change the constraint's constant property.Call layoutIfNeeded,then you can get the right frame.
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIButton *mainTimerView;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainTimerViewHeightConstraint;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view layoutIfNeeded];
NSLog(#"%#",#(self.mainTimerView.frame.size.width));
self.mainTimerViewHeightConstraint.constant = 100;
[self.view layoutIfNeeded];
NSLog(#"%#",#(self.mainTimerView.frame.size.height));
}
#end
Calling layoutIfNeeded aim to to force the layout of subviews before drawing, then viewDidLayoutSubviews will be called. Note that you can get the correct frame in viewDidLayoutSubviews. In other words, you can get the right frame after viewDidLayoutSubviews has been called.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(#"%#",NSStringFromCGRect(self.mainTimerView.frame));
}
1) Change the height constraint of the mainview.
2) Call the layoutIfNeeded method.
3) Do it on viewDidAppear.
Have you tried calling setNeedsLayout on the parent view? This call is used to ask a view to layout its subviews. From the setNeedsLayout's discussion section:
Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
Calling layoutIfNeeded might be another option. But according to the docs, it forces an immediate layout, and therefore does not provide the consolidation benefit gained by using setNeedsLayout.
layoutIfNeeded documention:
Lays out the subviews immediately.
Plz create the IBOutlet of height Constraint of parent view.
IBOutlet NSLayoutConstraint *RedViewHeightconstraint;
- (void)viewWillAppear:(BOOL)animated
{
RedViewHeightconstraint.constant = 500;
}
it will work for you
You just need to create IBOutlets of constraints of height and width of mainTimerView. Once you do so add these lines of code :
constraintOuterViewHt.constant = 200
constraintOuterViewWidth.constant = 200
You don't need to do anything else.
Check screen shot here

Resize xib UIView to fit UIButton inside

I have a UIView created from a xib file. Inside that UIView there is a UIButton with a text that may be longer or shorter depending on the language.
What I want is to resize the parent UIView to fit the width of the UIButton. How can I do it with AutoLayout, or programmatically?
Thanks,
add leading, trailing, top and bottom constraint of button
add width constraint of view and connect to viewWidthConstraint.
calculate width of button for language and set constant of viewWidthConstraint.
View and button will resize
#property (nonatomic, weak) IBOutlet NSLayoutConstraints *viewWidthConstraint;
- (void)viewDidLoad {
super.viewDidLoad();
self.viewWidthConstraint.constant = [self buttonWidth];
}
- (CGFloat)buttonWidth {
return 100.f; (calculate width of button)
}

Can't change Constraint IBOutlet that is defined for different size classes in IB

I made a special test app for this case. (I'm sorry it is already removed)
I added a view on my controller's view in Storyboard, set up AutoLayout constraints in Interface Builder and made one of them (vertical space) is defferent for different size classes. Screenshot from IB
So the value is 100 for Any height, Any width and 0 for Regular height, Regular width.
It works well, on iPhone vertical distance from top is 100, when on iPad it is 0.
Also I made IBOutlet for this constraint and want to change it in runtime to 10
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;
it seemed I couldn't change it because it gives no effect
- (void)viewDidLoad
{
[super viewDidLoad];
self.topVerticalConstraint.constant = 10; // it doesn't work
}
Although it works when I remove value for Regular height, Regular width in Interface Builder.
Am I miss something about the size classes?
The problem is that constraints are not fully defined yet until Layout events happen between -viewWillLayoutSubviews and -viewDidLayoutSubviews where all the parameters from IB comes into play.
My rule of thumb is:
if you use frames to position your views manually you can do it as early as -viewDidLoad,
if you use autolayout constraints for positioning, make adjustments as early as -viewDidLayoutSubviews;
The second statements only considers code adjustments to constraints that have been made in IB. Adjustments that you are making in -viewDidLoad will be overridden by parameters set in IB during layout. If you add constraints with code you can set them in -viewDidLoad, since there will be nothing to override them.
I've changed your code a bit and it works:
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;
#property (weak, nonatomic) IBOutlet UIView *square;
#property (assign, nonatomic) BOOL firstLayout;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.firstLayout = YES;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.firstLayout) {
self.topVerticalConstraint.constant = 10;
self.firstLayout = NO;
}
}
#end
Notice that -viewDidLayoutSubviews is called many times during the lifetime of a ViewController, so you have to make sure that your adjustments happen only once on initial load.
The problem:
If you set up different value for different size classes in IB for the constraint like this:
then you can't change constant value in code like this:
self.adHeightConstraint.constant = 0; // value set to 0
[self.view layoutIfNeeded]; // value get back to IB value (44 or 36)
In this situation you may see that your constant value persists only until views recalculates. So, after [self.view layoutIfNeeded] the value of constant reset back to whatever was set in IB.
The solution:
Add the second constraint of the same attribute (in my case it was the height) with desired value. You may set this value in IB or change it in the code.
Set low priority for this new constraint. Since it's low priority, it won't be any conflict.
Now when you need to apply the new constant, simple disable the first constraint:
self.adHeightConstraint.active = NO;
[self.view layoutIfNeeded];
I have experienced the same issue, but it doesn't seem to have anything to do with viewDidLoad vs viewDidLayoutSubviews. Interface Builder can manage alternate constraint constants for different size classes, but when you try to update NSLayoutConstraint.constant in code, that constant isn't associated with any particular size class (including the active one).
From Apple docs, Changing Constraint Constants for a Size Class (XCode 7, Interface Builder)
My solution has been to remove the alternate constants from IB, and manage the size-based constraint constant switch in code, only for those specific constraints that are updated/modified in code. Any constraints that are only managed via storyboard/IB can use the alternate-size constants as normal.
// XCode 7.0.1, Swift 2.0
static var isCompactHeight : Bool = false;
static var heightOffset : CGFloat {
return (isCompactHeight ? compactHeightOffset : regularHeightOffset);
}
/* applyTheme can be called as early as viewDidLoad */
func applyTheme() {
// This part could go wherever you handle orientation changes
let appDelegate = UIApplication.sharedApplication().delegate;
let window = appDelegate?.window;
let verticalSizeClass = window??.traitCollection.verticalSizeClass ?? UIUserInterfaceSizeClass.Unspecified;
isCompactHeight = (verticalSizeClass == UIUserInterfaceSizeClass.Compact);
// Use heightOffset
changingConstraint.constant = heightOffset;
}
I'm hoping that some later version of Swift/XCode introduces getters & setters that take size-based alternates into account, mirroring the functionality that's already available via IB.
I check same scenario in sample project it was working may be you are forget to connect NSLayoutConstraint topVerticalConstraint with storyboard.
Change constraint's constant in viewDidLayoutSubviews
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (IS_IPHONE4) {
self.topConstraint.constant = 10;
self.bottomButtonTop.constant = 10;
self.pageControlTopConstraint.constant = 5;
}
}
Easiest solution:
if (self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular && self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
// for iPhone
cnicTopConstraint.constant = -60;
} else {
// for iPad
cnicTopConstraint.constant = -120;
}

Xcode 5, how to use autolayout with orientation, in the shown scenario?

I'm struggling with AutoLayout, to arrange two buttons as shown below.
I've watched the initial 2012 WWDC video and the Xcode-5 WWDC update video.
Also various other videos and tutorials.
I'm having trouble describing the issue so I've produced an image to show the problem.
I can align simple layouts, like two buttons at the bottom of the screen and I've tried adding a container then adding the buttons to that.
I'm beginning to think this might not be possible.
Can someone advise?
The following seem like common behaviors that we might ask Auto Layout to perform:
move the top half of a layout in portrait to the left half in landscape
and, move the bottom half of a layout in portrait to the right half in landscape
Here's how I would do it. I would create two content views for the two halves of the layout. Although the content views would be created in IB, all of their constraints would merely be placeholders and removed at runtime (by checking a box in each constraint's Attributes inspector). In order to create placeholder constraints, however, you need to create constraints explicitly in IB. In code, the constraints for the content views are created dynamically in response to device rotation.
The advantage to this approach is that you can layout the subviews for the content views in IB and not have to worry about orientation. The configuration of the content views' constraints could be abstracted out into a UIViewController base class.
Here's a screenshot of the content views without any subviews. The root view is white and can be seen peaking behind the status bar.
Here's the code for configuring the content views' constraints:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *topContentView; // moves to the left half in landscape
#property (weak, nonatomic) IBOutlet UIView *bottomContentView; // moves to the right half in landscape
#property (nonatomic, strong) NSArray *constraintsForContentViews;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.constraintsForContentViews = #[];
[self configureConstraintsForContentViewsForInterfaceOrientation:UIInterfaceOrientationPortrait];
}
// instead of creating this helper method, this code could be placed in the view controller's updateViewConstraints method
- (void)configureConstraintsForContentViewsForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
UIView *topView = self.topContentView;
UIView *bottomView = self.bottomContentView;
UIView *leftView = self.topContentView; // unnecessary but improves readibility of visual format
UIView *rightView = self.bottomContentView; // unnecessary but improves readibility of visual format
id topGuide = self.topLayoutGuide;
id bottomGuide = self.bottomLayoutGuide;
NSArray *visualFormats = nil;
// remove prior constraints
[self.view removeConstraints:self.constraintsForContentViews];
self.constraintsForContentViews = #[];
// build visual formats for orientation
if (UIInterfaceOrientationIsPortrait(orientation)) {
// portrait
visualFormats = #[#"H:|[topView]|", #"H:|[bottomView]|", #"V:[topGuide][topView(bottomView)][bottomView(topView)][bottomGuide]"];
} else {
// landscape: topView becomes leftView by name only, bottomView becomes rightView by name only
visualFormats = #[#"H:|[leftView(rightView)][rightView(leftView)]|", #"V:[topGuide][leftView][bottomGuide]", #"V:[topGuide][rightView][bottomGuide]"];
}
// install new constraints
for (NSString *format in visualFormats) {
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:0 views:NSDictionaryOfVariableBindings(topView, bottomView, leftView, rightView, topGuide, bottomGuide)];
[self.view addConstraints:constraints];
self.constraintsForContentViews = [self.constraintsForContentViews arrayByAddingObjectsFromArray:constraints];
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self configureConstraintsForContentViewsForInterfaceOrientation:toInterfaceOrientation];
}
#end
One thing I've been doing is, on your "left pane", add top and left constraints, then height and width. On the "right pane", add bottom and right constraints, height and width.
Then on - willRotateToInterfaceOrientation:duration: you can adjust each constraint's constant to make the sizes exactly what you want.

Update the preferredMaxLayoutWidth for a multiline UILabel with unknown size (Auto Layout)

I have a custom view class which inherits from UIView. This class has an UILabel as its subview. In the init-function of this custom view class I set up everything needed like this:
//h-file
#import <UIKit/UIKit.h>
#interface MyCustomView : UIView
#property (strong, nonatomic) UILabel *myLabel;
#end
//m-file
#implementation MyCustomView
#synthesize myLabel = _myLabel;
- (id)init
{
self = [super init];
if (self) {
_myLabel = [UILabel new];
if(_textView){
_myLabel.highlightedTextColor = [UIColor whiteColor];
_myLabel.translatesAutoresizingMaskIntoConstraints = NO;
_myLabel.lineBreakMode = NSLineBreakByWordWrapping;
_myLabel.numberOfLines = 0;
_myLabel.backgroundColor = [UIColor clearColor];
[self addSubview:_myLabel];
}
}
return self;
}
#end
I also set up a bunch of constraints to manage padding inside my custom view - furthermore there are constraints to layout multiple MyCustomView-instances for both vertical and horizontal axis as well.
To get a multilined label output I have to set the preferredMaxLayoutWidth-property of the UILabel myLabel. The width depends on the free space available. At http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html I read, that I can let Auto Layout calculate the width first and set it as preferredMaxLayoutWidth after the frame of the MyCustomView-instance (the label inside is single lined at this moment) has been set.
If I put the following function into the MyCustomView, the label still has a single line of text:
- (void)layoutSubviews
{
[super layoutSubviews];
float width = _myLabel.frame.size.width;
_myLabel.preferredMaxLayoutWidth = width;
[super layoutSubviews];
}
If I set the preferredMaxLayoutWidth to an explicit value inside the init-function, the label is multilined.
Does anybody know what I am doing wrong here?
Thanks in advance!
Without seeing all the constrains you have setup for your custom view, and the superview that contains it, it's really hard to determine the problem, I suggest you to print out all the view frames of the entire view hierarchy starting from the view controller's view at viewDidLayoutSubviews and determine if the label and its superviews have correct frame set.
I have an encountered similar issues with dynamic label size and scroll view so I created a prototype here, might be useful to you too: https://github.com/briandotnet/AutoLayoutScrollViewExperiment

Resources