I'm playing with iOS9 uistackview and having problems to customize animation when adding element. You can notice that the yellow element position is being animated from top-left corner and starting with zero width.
I would like yellow element already being positioned below violet element and animate only height from 0 to some value (inverse behavior of green element animation)
I know its easy to do it when element is already in arrangeViews hierarchy unfortunately in my project I have to add and remove items dynamically at arbitrary index. Code below:
#interface ViewController ()
{
NSLayoutConstraint* heightConstraint2;
UIStackView *stackView;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//View 1
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 60)];
view1.backgroundColor = [UIColor blueColor];
[view1.heightAnchor constraintEqualToConstant:60].active = true;
[view1.widthAnchor constraintEqualToConstant:300].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;
heightConstraint2 = [view2.heightAnchor constraintEqualToConstant:100];
heightConstraint2.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
stackView = [[UIStackView alloc] init];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.distribution = UIStackViewDistributionEqualSpacing;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 0;
[stackView addArrangedSubview:view1];
[stackView addArrangedSubview:view2];
[stackView addArrangedSubview:view3];
stackView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:stackView];
}
-(void)viewDidAppear:(BOOL)animated {
heightConstraint2.constant = 0;
UIView *view4 = [[UIView alloc] init];
view4.backgroundColor = [UIColor yellowColor];
[view4.heightAnchor constraintEqualToConstant:80].active = true;
[view4.widthAnchor constraintEqualToConstant:180].active = true;
NSLayoutConstraint* heightConstraint4 = [view4.heightAnchor constraintEqualToConstant:0];
[stackView insertArrangedSubview:view4 atIndex:3];
// [stackView layoutIfNeeded];
heightConstraint2.constant = 0;
heightConstraint4.constant = 80;
[UIView animateWithDuration:0.5
delay:3
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
[stackView layoutIfNeeded];
}
completion:nil];
}
Add your view but set it as hidden, Then in animationBlock, set hidden = true
Related
Scenario: Refresh Control containing image views.
I'm sure this problem has been experienced:
Create a UIRefreshControl upon a UICollectionView.
Do a horizontal scroll to notice that the UIRefreshControl is skewed.
Here's the setup code:
- (void)setupRefreshControl {
self.refreshControl = [UIRefreshControl new];
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Refresh Data"];
self.refreshControl.tintColor = UIColor.redColor;
self.refreshControl.backgroundColor = UIColor.whiteColor;
UIImage *hook = [UIImage imageNamed:#"Hook"];
UIImageView *hookImageView = [[UIImageView alloc] initWithImage:hook];
UIImage *fish = [UIImage imageNamed:#"Fish"];
UIImageView *fishImageView = [[UIImageView alloc] initWithImage:fish];
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:#[hookImageView ,fishImageView]];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.spacing = 10.0;
[self.refreshControl addSubview:stackView];
[stackView setTranslatesAutoresizingMaskIntoConstraints:NO];
UILayoutGuide *container = [_refreshControl layoutMarginsGuide];
[stackView.topAnchor constraintEqualToAnchor:container.topAnchor].active = YES;
CGFloat width = self.refreshControl.bounds.size.width;
[stackView.leftAnchor constraintEqualToAnchor:container.leftAnchor constant:width/2].active = YES;
[self.refreshControl addTarget:self action:#selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
self.collectionView.refreshControl = self.refreshControl;
}
As you can see, the UIRefreshControl is off-centered upon a horizontal scroll. Do I need to always calculate the new center?
There must be a standard way to do this.
The simplest way should be centering the stack view with auto layout, and I would not embed the stack view inside of a refresh control. The following constraints (except the 20.0 which you should adapt to your needs) should place the stack view on the right position:
stackView.backgroundColor = [UIColor clearColor];
[stackView.centeredXAnchor constraintEqualToAnchor:container.centeredXAnchor constant:0.0].active = YES;
[stackView.bottomAnchor constraintEqualToAnchor:container.topAnchor constant:20.0].active = YES;
You should also add an activity view (or animate the fish icon) and a label to the stack view, and you have to handle the bouncing by yourself.
Simple oversight:
I merely needed an addition anchors to center the UIRefreshControl:
- (void)setupRefreshControl {
// Refresh Control:
self.refreshControl = [UIRefreshControl new];
self.collectionView.refreshControl = self.refreshControl;
[_refreshControl setTranslatesAutoresizingMaskIntoConstraints:NO];
UILayoutGuide *refreshControlContainer = [_collectionView layoutMarginsGuide];
[_refreshControl.topAnchor constraintEqualToAnchor:refreshControlContainer.topAnchor constant: 1.0].active = YES;
[_refreshControl.leftAnchor constraintEqualToAnchor:refreshControlContainer.leftAnchor constant: 1.0].active = YES;
[_refreshControl.heightAnchor constraintEqualToConstant:300.0].active = YES;
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Refresh Data"];
self.refreshControl.tintColor = UIColor.redColor;
self.refreshControl.backgroundColor = UIColor.whiteColor;
// StackView members:
UIImage *hook = [UIImage imageNamed:#"Hook"];
UIImageView *hookImageView = [[UIImageView alloc] initWithImage:hook];
UIImage *fish = [UIImage imageNamed:#"Fish"];
UIImageView *fishImageView = [[UIImageView alloc] initWithImage:fish];
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:#[hookImageView ,fishImageView]];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.spacing = 10.0;
[self.refreshControl addSubview:stackView];
// StackView:
[stackView setTranslatesAutoresizingMaskIntoConstraints:NO];
UILayoutGuide *stackViewContainer = [_refreshControl layoutMarginsGuide];
[stackView.topAnchor constraintEqualToAnchor:stackViewContainer.topAnchor].active = YES;
CGFloat width = self.refreshControl.bounds.size.width;
[stackView.leftAnchor constraintEqualToAnchor:stackViewContainer.leftAnchor constant:width/2].active = YES;
[self.refreshControl addTarget:self action:#selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
}
hello i am using constraints to show the view as desired horizontally and vertically an it is working well,
But now according to my requirement i have to use constraints in a seance that if i hide(set hidden) any of the placed button or label then remaining buttons or labels have to adjsut automatially.
for ex :- i am using four button in a series with equal spacing(filling the view). Now if i hide one of the button from the code then remaining three buttons should adjust themseleves according to space
I want this:
to convert to:
if i hide one of the four buttons.
Instead of hiding the button, you can animate and change it's width constraint to 0. This will probably look better also
i.e something like this:
button2WidthConstraint.constant = 0
UIView.animateWithDuration(0.3) { () -> Void in
self.view.setNeedsLayout()
}
This is kinda difficult using autolayout only. Even if you just change the width constraint to 0 you're gonna have to deal with the spacings. You will need to adjust spacing for edge buttons differently then for buttons in middle - when you change width to 0, there's still left and right constraint and you need to determine which one to zero out as well.
One approach is to use UIStackView which handles this case automatically for you. You can set spacing and distribution of buttons in storyboard, made little sample app so you can see the settings yourself.
After that you just call: removeFromSuperview and the UIStackView distributes other buttons for you. You can animate the transition as well.
i have tried to solve the problem this way, you can change it as per your requiremne. I have taken UIView(you can take UIButton here) and UIStackView(horizontal axis).
-(void)createOrUpdateUI
{
//View 1
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor blueColor];
[view1.heightAnchor constraintEqualToConstant:40].active = true;
[view1.widthAnchor constraintEqualToConstant:60].active = true;
//View 2
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor greenColor];
[view2.heightAnchor constraintEqualToConstant:40].active = true;
[view2.widthAnchor constraintEqualToConstant:60].active = true;
//View 3
UIView *view3 = [[UIView alloc] init];
view3.backgroundColor = [UIColor magentaColor];
[view3.heightAnchor constraintEqualToConstant:40].active = true;
[view3.widthAnchor constraintEqualToConstant:60].active = true;
//View 4
UIView *view4 = [[UIView alloc] init];
view4.backgroundColor = [UIColor magentaColor];
[view4.heightAnchor constraintEqualToConstant:40].active = true;
[view4.widthAnchor constraintEqualToConstant:60].active = true;
//Stack View
UIStackView *stackView = [[UIStackView alloc] init];
stackView.axis = UILayoutConstraintAxisHorizontal;
stackView.distribution = UIStackViewDistributionEqualSpacing;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 30;
NSArray *viewArray = [[NSArray alloc] initWithObjects:view1, view2, view3, nil];
for (UIView *view in viewArray) {
[stackView addArrangedSubview:view];
}
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;
}
and it outpit's as :
in above code just change line :
NSArray *viewArray = [[NSArray alloc] initWithObjects:view1, view2, view3, nil];
to
NSArray *viewArray = [[NSArray alloc] initWithObjects:view1, view2, view3, view4, nil];
and output is :
Hope it helps.
I'm creating a scrollView programmatically, and all the views in it. However, when I add more then 1 view, the other views layers don't show up right in their respective frames.
For example:
-(void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, 150*5);
self.scrollView.backgroundColor = [UIColor lightGrayColor];
self.scrollView.scrollEnabled = YES;
self.scrollView.showsVerticalScrollIndicator = NO;
[self.view addSubview:self.scrollView];
[self setUpLogInButtons];
}
Then I set the subViews to scrollView up here:
-(void)setUpLogInButtons {
CGFloat subviewWidth = self.scrollView.frame.size.width/2;
CGFloat subviewHeight = 150;
UIView *facebookView = [[UIView alloc] init];
facebookView.frame = CGRectMake(0, 0, subviewWidth, subviewHeight);
[facebookView.layer insertSublayer:[self gradientLayerForViewFrame:facebookView.frame withColors:[self colorArrayWithValues:0x4A5C81 v2:0x495B82 v3:0x495B82 v4:0x475B85 v5:0x465B87 v6:0x455A88 v7:0x445A8A v8:0x435A8B v9:0x425A8D v10:0x41598F v11:0x405990 v12:0x3F5992 v13:0x3E5993 v14:0x3D5895 v15:0x3C5896 v16:0x3B5898 v17:0x3A589A]] atIndex:0];
[facebookView addSubview:[self label:self.facebookLabel forView:facebookView withName:#"Facebook" loggedIn:NO]];
[self.scrollView addSubview:facebookView];
}
When it's one subView it works fantastic, when I add the other one it's not right:
-(void)setUpLogInButtons {
...
UIView *facebookView = [[UIView alloc] init];
...
[self.scrollView addSubview:facebookView];
UIView *twitterView = [[UIView alloc] init];
twitterView.frame = CGRectMake(subviewWidth, 0, subviewWidth, subviewHeight);
[twitterView.layer insertSublayer:[self gradientLayerForViewFrame:twitterView.frame withColors:[self colorArrayWithValues:0x2BA8D5 v2:0x28A8D6 v3:0x25A8D8 v4:0x22A8D9 v5:0x20A9DB v6:0x1DA9DC v7:0x1AA9DE v8:0x18AADF v9:0x15AAE1 v10:0x12AAE3 v11:0x10ABE4 v12:0x0DABE6 v13:0x0AABE7 v14:0x08ACE9 v15:0x05ACEA v16:0x02ACEC v17:0x00ADEE]] atIndex:0];
[twitterView addSubview:[self label:self.twitterLabel forView:twitterView withName:#"Twitter" loggedIn:NO]];
[self.scrollView addSubview:twitterView];
}
This one does not show up, the layers specifically are off. However, here is the kicker, the label shows in the right spot because the labels frame is based on it's parent view frame.
-(UILabel *)label:(UILabel *)viewLabel forView:(UIView *)accountView withName:(NSString *)name loggedIn:(BOOL)loggedinStatus {
UILabel *label = viewLabel;
label.frame = CGRectMake(0, accountView.frame.size.height - 50, accountView.frame.size.width, 50);
label.text = [self setAccountLabelForName:name loggedIn:loggedinStatus];
label.textColor = [UIColor whiteColor];
label.textAlignment = NSTextAlignmentLeft;
return label;
}
here is a picture that shows you that the label is based off the parents view frame, and it shows up correctly, but the gradient does not, and it's based off the same frame.
and my gradient method:
-(CAGradientLayer *)gradientLayerForViewFrame:(CGRect)viewFrame withColors:(NSArray *)colors {
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = viewFrame;
gradient.colors = colors;
return gradient;
}
For some reason when inserting my gradient layer to the views layer I had to change it:
//INSTEAD OF THIS:
[... insertSublayer:[self gradientLayerForViewFrame:facebookView.frame...];
//THIS WORKED FLAWLESSLY:
[... insertSublayer:[self gradientLayerForViewFrame:facebookView.bounds...];
I'm trying to implement a check if a view is bounds of another view to do something. This my code:
CGRect movingView = [self.viewController.view convertRect:recognizer.view.frame toView:self.viewController.view];
for (NSUInteger i = 0; i < arrayOfViewsToCheck.count; i++)
{
UIView *view = resultArray [0];
CGRect boxframe = [self.viewController.view convertRect:view.frame toView:self.viewController.view];
if (CGRectIntersectsRect(movingView.frame, view.frame) )
{
isInBounds = YES;
}
}
But when I execute my code and for example I have this two views:
(CGRect) $6 = origin=(x=569, y=513) size=(width=76, height=100)
(CGRect) $7 = origin=(x=358, y=520) size=(width=116, height=100)
are in bounds of each other but of each I don't know what I'm doing wrong because it seems like they are not on my code.
I'll really appreciate if can please let me know what I'm doing wrong trying to detect if the views are in bounds of each other
Your use of CGRectIntersectsRect is correct, but the rectangles in your example do not actually intersect. Here's what they look like:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect rect1 = CGRectMake(569,513,76,100);
CGRect rect2 = CGRectMake(368,520,116,100);
UIView *view1 = [[UIView alloc] initWithFrame:rect1];
UIView *view2 = [[UIView alloc] initWithFrame:rect2];
view1.backgroundColor = [UIColor redColor];
view2.backgroundColor = [UIColor blueColor];
[self.view addSubview:view1];
[self.view addSubview:view2];
}
And with a slightly modified version:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect rect1 = CGRectMake(469,513,76,100); /* Note 469 instead of 569 */
CGRect rect2 = CGRectMake(368,520,116,100);
UIView *view1 = [[UIView alloc] initWithFrame:rect1];
UIView *view2 = [[UIView alloc] initWithFrame:rect2];
view1.backgroundColor = [UIColor redColor];
view2.backgroundColor = [UIColor blueColor];
[self.view addSubview:view1];
[self.view addSubview:view2];
if (CGRectIntersectsRect(rect1, rect2) ) {
NSLog(#"Yay we intersect!");
}
}
The scroll view shows up, without any of the views visible. The view moves fine, the page control works. When i change the scroller background that all works fine. The views "the different colours" don't show. Have no idea whats wrong. Thanks for the help
-(void)viewDidLoad {
[super viewDidLoad];
CGRect frame = CGRectMake(0, 215, WIDTH_OF_IPHONE, HEIGHT_OF_SCROLLER);
int numOfPhotos = 6;
scrollViewer = [[UIScrollView alloc] initWithFrame:frame];
scrollViewer.contentSize = CGSizeMake((WIDTH_OF_IPHONE*numOfPhotos), HEIGHT_OF_SCROLLER);
scrollViewer.pagingEnabled = YES;
//scrollViewer.backgroundColor = [UIColor lightGrayColor];
[scrollViewer setDelegate:self];
int i = 0;
while (i < numOfPhotos){
CGRect frameOfView = CGRectMake((i*320), 215, 320, HEIGHT_OF_SCROLLER);
UIView *photoFrame = [[UIView alloc] initWithFrame:frameOfView];
if (i == 0){
photoFrame.backgroundColor = [UIColor redColor];//colorWithPatternImage:image1];
NSLog(#"colour was selected");
}else if (i==1){
photoFrame.backgroundColor = [UIColor blueColor];//colorWithPatternImage:image2];
}else if (i==2){
photoFrame.backgroundColor = [UIColor purpleColor];//colorWithPatternImage:image3];
}else if (i==3){
photoFrame.backgroundColor = [UIColor orangeColor];//colorWithPatternImage:image4];
}else if (i==4){
photoFrame.backgroundColor = [UIColor magentaColor];//colorWithPatternImage:image5];
}else{
photoFrame.backgroundColor = [UIColor greenColor];//colorWithPatternImage:image6];
}
[scrollViewer addSubview:photoFrame];
//[photoFrame release];
photoFrame = NULL;
i++;
}
pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, 0, WIDTH_OF_IPHONE, 15)];
pageControl.center = CGPointMake(scrollViewer.center.x, (208+HEIGHT_OF_SCROLLER));
[pageControl setNumberOfPages:numOfPhotos];
[pageControl addTarget:self action:#selector(pageSwiped:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:scrollViewer];
[self.view addSubview:pageControl];
My .H file looks like this
#interface HomePage : UIViewController <UIActionSheetDelegate, UIScrollViewDelegate> {
IBOutlet UIScrollView *scrollViewer;
UIPageControl *pageControl;
BOOL pageControlUsed;
}
#end
Try setting the y origin of the photoFrame to 0 as it will place it under the actual frame of your scrollview.
If not the problem you are having is that you are making the photoFrame NULL. If you are using ARC you shouldn't worry as the Garbage Collector will take care of it.