Setting the maximum value of a UISlider causing strange behaviour - ios

I am having a really weird issue trying to use a UISlider, when I change the maximumValue of the slider to > 1.0, I get this weird duplicate slider appearing on my view. Please help :)
Heres what it looks like when slider.maximumValue = 10.0;
http://imagizer.imageshack.us/v2/800x600q90/827/kf12.png
Heres what it looks like when slider.maximumValue = 1.0;
http://imagizer.imageshack.us/v2/800x600q90/197/bcvf.png
Is there some behaviour of a UISlider I am missing? Here is my code for configuring the slider...
-(void)layoutSubviews
{
[super layoutSubviews];
CGSize viewSize = self.contentSubview.bounds.size;
self.timeSlider.frame = CGRectMake(viewSize.width / 2 - 80,
viewSize.height / 2 - 12,
viewSize.width / 2,
24);
-(void)configureSubviews
{
[super configureSubviews];
self.timeSlider = [[UISlider alloc] initWithFrame:CGRectZero];
self.timeSlider.continuous = YES;
self.timeSlider.minimumValue = 0;
self.timeSlider.maximumValue = 1.0f;
[self.timeSlider addTarget:self action:#selector(timeValueChanged:) forControlEvents:UIControlEventValueChanged];
[self.contentSubview addSubview:self.timeSlider];
}
-(void)timeValueChanged:(UISlider *)slider
{
self.timeValueLabel.text = [NSString stringWithFormat:#"%d min", (int)floorf(slider.value)];
[self layoutSubviews];
}

layoutSubviews
Lays out subviews.
- (void)layoutSubviews
Discussion
The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews.
Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.
Referance by apple doc

Related

When to layout subviews of UIViewControllers view?

In my UIViewController class, I'm creating a UIView called safeAreaView and adding it as a subview to the UIViewControllers view property. I'm making it so safeAreaView takes up the entire safe area of the UIViewControllers view property:
- (void) viewDidLoad
{
[self setToolbarWithColor: self.mainToolbarColor animated:NO];
self.tapGestureRecognizer.delegate = self;
self.view.clipsToBounds = YES;
self.view.backgroundColor = [UIColor lightGrayColor];
self.safeAreaView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
self.safeAreaView.clipsToBounds = YES;
self.safeAreaView.delegate = self;
self.safeAreaView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview: self.safeAreaView];
[self.safeAreaView.leadingAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.leadingAnchor].active = YES;
[self.safeAreaView.trailingAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.trailingAnchor].active = YES;
[self.safeAreaView.topAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.topAnchor].active = YES;
[self.safeAreaView.bottomAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.bottomAnchor].active = YES;
[self.safeAreaView loadSubviews];
}
This works fine, but my problem is, at some point after this during the UIViewControllers initialization cycle, safeAreaView updates to account for the statusbar (it's y position moves up 20 and it decreases in size by 20).
I need to layout some subviews on safeAreaView and I don't know the proper time? If I attach the subviews like above, they have the wrong height. And I can't use some auto layout features on the subviews because there are specific things that I need to do. I've also tried executing the above code in viewWillAppear with no luck.
Wondering if anyone had any suggestions?
You can override - (void)layoutSubviews on your safeAreaView class:
- (void)layoutSubviews {
[super layoutSubviews];
// Manual frame adjustment for non-autolayout participating subviews
// ...
}
Another option would be to override your safeAreaView's class frame setter, so each time the frame of your view changes, you'll get a chance to manually set any subview frames as needed.

When should I be setting borders in UIViews?

I've got a UIView that does not fill the whole screen and I would like to add a top border to that view. However, I keep getting the following:
Here is the code I am using:
CGFloat thickness = 4.0f;
CALayer *topBorder = [CALayer layer];
topBorder.frame = CGRectMake(0, 0, self.announcementCard.frame.size.width, thickness);
topBorder.backgroundColor = [UIColor blueColor].CGColor;
How I know why the border goes off the screen. This is because I put the border on the view inside the UIViews init method. When I do this the self.announcementCard.frame.size.width is 1000 and hence why the border goes off the screen. The self.announcementCard.frame.size.width has a width and height of 1000. The reason for this is because the UIView hasn't added the constraints to the UIView in its init methods.
Thus, my question is when should I be calling the code I've written above? When will self.announcementCard.frame.size.width have its constraints added to it and have its frame updated?
You should add your subviews (or sublayers) in the viewDidLoad method. However if you are using the auto-layout keep a reference of your sublayer and update it in the viewDidLayoutSubviews method:
- (void)viewDidLoad {
[super viewDidLoad];
_borderLayer = [CALayer layer];
[self.view.layer addSublayer:_borderLayer];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_borderLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, 3);
}
Otherwise you can simply clipsToBounds the view to avoid the subviews to be visible beyond the bounds.
self.view.clipsToBounds = YES;
in the init the graphics isn't made yet. You have to put all your configuration on graphics object in the viewDidLoad: or viewWillAppear: of the UIViewController.
Short Answer:
viewWillAppear
By the time viewWillAppear is called, your subviews have been laid out and the frames are valid. Doing frame-based calculations in viewDidLoad can often have issues since the frames have not been set.

How to implement sizeToFit if it depends on layoutSubviews?

In the documentation of layoutSubviews, Apple says:
You should not call this method directly.
I'm trying to implement sizeToFit. I want it to put a tight bounding box on all of the subviews. I have to layout the subviews before determining such a bounding box. That means I must call layoutSubviews, which Apple frowns upon. How would I solve this dilemma without violating Apple's rules?
- (void)layoutSubviews
{
[super layoutSubviews];
self.view0.frame = something;
self.view1.frame = somethingElse;
}
- (void)sizeToFit
{
[self layoutSubviews];
self.frame = CGRectMake(
self.frame.origin.x,
self.frame.origin.y,
MAX(
self.view0.frame.origin.x + self.view0.frame.size.width,
self.view1.frame.origin.x + self.view1.frame.size.width
),
MAX(
self.view0.frame.origin.y + self.view0.frame.size.height,
self.view1.frame.origin.y + self.view1.frame.size.height
)
);
}
One should not override -sizeToFit. Instead override -sizeThatFits: which is internally called by -sizeToFit with the view's current bounds size.
You should not override this method. If you want to change the default sizing information for your view, override the sizeThatFits: instead. That method performs any needed calculations and returns them to this method, which then makes the change. – UIView Class Reference
Also not that even if you would override -sizeToFit, there is most likely no reason to perform layout immediately. You only size the view, i.e. set its bounds size. This triggers a call to -setNeedsLayout, marking the view as needing layout. But unless you want to animate the view, the new layout does not have to be applied right away.
The point of this delayed update pattern is that it saves a lot of time if you perform multiple consecutive updates, since the actual update is only performed once.
I typically do this. It works like a charm.
#pragma mark - Layout & Sizing
- (void)layoutSubviews
{
[self calculateHeightForWidth:self.bounds.size.width applyLayout:YES];
}
- (CGSize)sizeThatFits:(CGSize)size
{
CGFloat const width = size.width;
CGFloat const height = [self calculateHeightForWidth:width applyLayout:NO];
return CGSizeMake(width, height);
}
- (CGFloat)calculateHeightForWidth:(CGFloat)width applyLayout:(BOOL)apply
{
CGRect const topViewFrame = ({
CGRect frame = CGRectZero;
...
frame;
});
CGRect const bottomViewFrame = ({
CGRect frame = CGRectZero;
...
frame;
});
if (apply) {
self.topView.frame = topViewFrame;
self.bottomView.frame = bottomViewFrame;
}
return CGRectGetMaxY(bottomViewFrame);
}
Note that the sample code is for a view that can be displayed at any width and the container would ask for the preferred height for a certain width.
One can easily adjust the code for other layout styles though.
I think it's very rare that you will need to use frame when using Auto Layout. Considering you want to solve the problem without breaking Apple rules, I would suggest using the Auto Layout way:
Setup constraints to determine the size of the container view.
Basically the constraints are setup in a way so that the container's width and height can be determined by the auto layout system.
For example, You can setup the constraints like:
Note that view0 and view1 must set both width and height constraints.
When you instantiate the view somewhere in your project, you don't need to setup the width and height constraint anymore. Auto layout will guess its size (called intrinsicContentSize) by the constraints you previously setup.
If you want to force layoutSubviews to happen, but don't want to call it directly, set the needsLayout flag, and then ask it to layout.
[self setNeedsLayout];
[self layoutIfNeeded];

iOS: determine when all children have had layoutSubviews called

it appears that viewDidLayoutSubviews is called immediately after layoutSubviews is called on a view, before layoutSubviews is called on the subviews of that view. Is there any way of knowing when layoutSubviews has been called on a view and all of its children that also needed their layouts updated?
You shouldn't have to know if the subviews of a subview have updated their layout: That sounds like too tight coupling. Also, each subview might handle the arrangement of their respective subviews differently and might not (need to) call layoutSubviews for its subviews at all. You should only ever have to know about your direct subviews. You should treat them more or less as black boxes and not care whether they have subviews of their own or not.
As #Johannes Fahrenkrug said, you should "treat them as black boxes". But according to my understandings, it is because that Cocoa just can't promise it.
If you really need to be notified when all subviews have done the layout job, here is a hardcore sample may solve your problem. I don't either promise it would work under every situation.
- (void) layoutSubviewsIsDone{
// Your code here for layoutSubviews is done
}
// Prepare two parameters ahead
int timesOfLayoutSubviews = 0;
BOOL isLayingOutSubviews = NO;
// Override the layoutSubviews function
- (void) layoutSubviews{
isLayingOutSubviews = YES; // It's unsafe here!
// you may move it to appropriate place according to your real scenario
// Don't forget to inform super
[super layoutSubviews];
}
// Override the setFrame function to monitor actions of layoutSubviews
- (void) setFrame:(CGRect)frame{
if(isLayingOutSubviews){
if(frame.size.width == self.frame.size.width
&& frame.size.height == self.frame.size.height
&& frame.origin.x == self.frame.origin.x
&& frame.origin.y == self.frame.origin.y
&& timesOfLayoutSubviews ==self.subviews.count){
isLayingOutSubviews = NO;
timesOfLayoutSubviews = 0;
[self layoutSubviewsIsDone]; // Detected job done, call your function
}else{
timesOfLayoutSubviews++;
}
}

Autolayout and shadow

I've got a problem with adding shadow to my UIView which is created in iOS 6 application with Autolayout.
Let's assume I have a method that adds a shadow on the bottom of UIView (this is actually a Category of UIView, so it's reusable):
- (void) addShadowOnBottom {
self.layer.shadowOffset = CGSizeMake(0, 2);
self.layer.shadowOpacity = 0.7;
self.layer.shadowColor = [[UIColor blackColor] CGColor];
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
}
When I call this method in viewDidLoad of some UIViewController, shadow is not added, probably due to all constraints, that have to be calculated.
When I call this method in viewWillAppear the same situation.
When I call this method in viewDidAppear it works, but when new view shows up there is a short moment when there is no shadow and it appears after a while.
If I resign from setting the shadowPath and remove line self.layer.shadowPath everything works, but view transitions are not smooth.
So my question is what is the right way to add a shadow to view in iOS 6 with Autolayout turned on ?
Another thing you can add to the layer when working with AutoLayout and you need a shadow on a UIView where the frame is not yet known is this :
self.layer.rasterizationScale = [[UIScreen mainScreen] scale]; // to define retina or not
self.layer.shouldRasterize = YES;
Then remove the shadowPath property because the auto layout constraints are not yet processed, so it's irrelevant. Also at the time of execution you will not know the bounds or the frame of the view.
This improves performance a lot!
i removed self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
from your code and it is working for me in viewDidLoad, please confirm.
Increasing shdowOffset will make you see the shadow more clear.
Having the exact same issue...
Although I am unable to get the CALayer shadow on a view to animate nicely, at least the shadow does re-align properly after animation.
My solution (which works fine in my application) is the set the shadowOpacity to 0, then reset it to the desired value AFTER the animation has completed. From a user's perspective, you cannot even tell the shadow is gone because the animations are typically too fast to perceive the difference.
Here is an example of some code in my application, in which I am changing the constant value of a constraint, which is 'trailing edge to superview' NSLayoutContraint:
- (void) expandRightEdge
{
[self.mainNavRightEdge setConstant:newEdgeConstant];
[self updateCenterContainerShadow];
[UIView animateWithDuration:ANIMATION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"PanelLayoutChanged" object:nil];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
nil;
}];
}
- (void) updateCenterContainerShadow
{
self.centerContainer.layer.masksToBounds = NO;
self.centerContainer.layer.shadowOpacity = 0.8f;
self.centerContainer.layer.shadowRadius = 5.0f;
self.centerContainer.layer.shadowOffset = CGSizeMake(0, 0);
self.centerContainer.layer.shadowColor = [UIColor blackColor].CGColor;
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:self.centerContainer.layer.bounds].CGPath;
[self.centerContainer.layer setShadowPath:shadowPath];
// Schedule a time to fade the shadow back in until we can figure out the CALayer + Auto-Layout issue
[self performSelector:#selector(fadeInShadow) withObject:nil afterDelay:ANIMATION_DURATION+.05];
}
- (void) fadeInShadow
{
[self.centerContainer.layer setShadowOpacity:0.8f];
}
Two things:
I could have put the fadeInShadow in the completion block, but due to the way some of my other code is factored, this works better for me.
I realize I am not performing a fade in with "fadeInShadow", but given how quickly it renderes after the completion of the animation, I found it is not necessary.
Hope that helps!

Resources