Auto Layout for dynamic/draggable UIView - ios

I am trying to have a draggable UIView within my ViewController, eventually this UIView will become a clickable button but for now, I am stuck with updating the constraints of this view when it is dragged on the screen. The code is below.
- (void)viewDidLoad
{
[super viewDidLoad];
self.anotherView = [[UIView alloc]init];
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(dragging:)];
[self.anotherView addGestureRecognizer:panRecognizer];
[self.anotherView setBackgroundColor: [UIColor blueColor]];
self.anotherView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview: self.anotherView];
[self.view bringSubviewToFront:self.anotherView];
NSDictionary *views = #{#"subview" : self.anotherView, #"superview" : self.view};
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:Nil
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:20]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[subview(40)]"
options:0
metrics:nil
views:views]];
// add horizontal constraints
/*[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subview(==20)]|" options:0 metrics:nil views:views]];
// set the height of the offscreen subview to be the same as its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[subview(==40)]" options:0 metrics:nil views:views]];
// set the location of the subview to be just off screen below the current view
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.anotherView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:self.view.bounds.size.height];
[self.view addConstraint:constraint];*/
//[self.anotherView pinEdges:JRTViewPinLeftEdge toSameEdgesOfView:self.view];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
}
The above code is how I have setup the UIView within the ViewController. It is expected to be of size 20x45. The code for dragging is below:
-(void)dragging:(UIPanGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateBegan)
{
//NSLog(#"Received a pan gesture");
self.panCoord = [gesture locationInView:gesture.view];
[self.tableView setScrollEnabled:NO];
}
CGPoint newCoord = [gesture locationInView:gesture.view];
float dX = newCoord.x-self.panCoord.x;
float dY = newCoord.y-self.panCoord.y;
CGFloat newXPoint;
CGFloat newYPoint;
gesture.view.frame = CGRectMake(gesture.view.frame.origin.x+dX, gesture.view.frame.origin.y+dY, gesture.view.frame.size.width, gesture.view.frame.size.height);
if (gesture.state == UIGestureRecognizerStateEnded) {
newYPoint = gesture.view.frame.origin.y+dY;
if (gesture.view.frame.origin.x+dX<(self.view.frame.size.width/2)) {
newXPoint = 0;
}
else {
newXPoint = self.view.frame.size.width - gesture.view.frame.size.width;
//The below is just an attempt to remove and re-add constraints so the view will stick to the right side of the screen. It is not working :(
[self.view removeConstraint:[NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
}
// self.draggedViewCoordinate = gesture.view.frame.origin;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.70];
[UIView setAnimationDelay:0.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[self.anotherView layoutIfNeeded];
[UIView commitAnimations];
[self.tableView setScrollEnabled:YES];
}
}
I am not getting how to update the constraints so the view will stick to the new dragged location. Can anyone help on this?

The accepted answer is incorrect. It's very expensive operation for UI to add/remove constraints.
Proper solution would be changing
self.yCoordinate.constant
and
self.xCoordinate.constant
properties based on dX and dY.

Okay, I solved it with the below code!
-(void)dragging:(UIPanGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateBegan)
{
//NSLog(#"Received a pan gesture");
self.panCoord = [gesture locationInView:gesture.view];
[self.tableView setScrollEnabled:NO];
}
CGPoint newCoord = [gesture locationInView:gesture.view];
float dX = newCoord.x-self.panCoord.x;
float dY = newCoord.y-self.panCoord.y;
CGFloat newXPoint;
CGFloat newYPoint;
gesture.view.frame = CGRectMake(gesture.view.frame.origin.x+dX, gesture.view.frame.origin.y+dY, gesture.view.frame.size.width, gesture.view.frame.size.height);
if (gesture.state == UIGestureRecognizerStateEnded) {
newYPoint = gesture.view.frame.origin.y+dY;
CGFloat newYPosition = (self.view.frame.size.height/2) - newYPoint - 20;
if (gesture.view.frame.origin.x+dX<(self.view.frame.size.width/2)) {
[self.view removeConstraint:self.xCoordinate];
[self.view removeConstraint:self.yCoordinate];
self.xCoordinate = [NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0];
[self.view addConstraint:self.xCoordinate];
self.anotherView.translatesAutoresizingMaskIntoConstraints = NO;
self.yCoordinate = [NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:-newYPosition];
[self.view addConstraint:self.yCoordinate];
}
else {
newXPoint = self.view.frame.size.width - gesture.view.frame.size.width;
[self.view removeConstraint:self.xCoordinate];
[self.view removeConstraint:self.yCoordinate];
self.xCoordinate = [NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0];
[self.view addConstraint:self.xCoordinate];
self.anotherView.translatesAutoresizingMaskIntoConstraints = NO;
self.yCoordinate = [NSLayoutConstraint constraintWithItem:self.anotherView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:-newYPosition];
[self.view addConstraint:self.yCoordinate];
}
// self.draggedViewCoordinate = gesture.view.frame.origin;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.70];
[UIView setAnimationDelay:0.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[self.anotherView layoutIfNeeded];
// gesture.view.frame = CGRectMake(newXPoint, newYPoint, gesture.view.frame.size.width, gesture.view.frame.size.height);
[UIView commitAnimations];
[self.tableView setScrollEnabled:YES];
}
}
Essentially, I maintained a reference to the constraints. This helped me in removing/re-adding the constraints based on the position of the dragged view.

This simplified version solved for me when dragging down and up:
#implementation SomeController
{
float beginDragPointY;
NSLayoutConstraint someConstraint;
}
- (void) viewDidLoad
{
// Define someConstraint
}
- (void) onDragGesture: (id) sender
{
UIPanGestureRecognizer* recognizer = (UIPanGestureRecognizer*)sender;
UIGestureRecognizerState state = recognizer.state;
CGPoint translation = [sender translationInView:self.view];
switch(state)
{
case UIGestureRecognizerStateBegan:
NSLog(#"Translation began %f %f", translation.x, translation.y);
beginDragPointY = translation.y;
break;
case UIGestureRecognizerStateChanged:
showBasicEventAnnotationConstraint.constant += translation.y - beginDragPointY;
beginDragPointY = translation.y;
[self.view setNeedsLayout];
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStatePossible:
default:
break;
}
[self.view layoutIfNeeded];
}

Related

Can't able to click a UICollectionViewCell in a UICollectionView

Let's say I have a view controller OptionsToCreateViewController which inherits UICollectionViewController and I have used that View Controller in another view controller like this.. I can able to see the view but can't able to click the cell
self.optionsToCreateViewController = [[OptionsToCreateViewController alloc] init];
self.optionsToCreateViewController.view.translatesAutoresizingMaskIntoConstraints = FALSE;
[self.optionsToCreateViewController didMoveToParentViewController:self];
[self addChildViewController:self.optionsToCreateViewController];
[self.view addSubview:self.optionsToCreateViewController.view];
OrganizerBottomBar *orgBB = [OrganizerBottomBar new];
CGRect applicationFrame = [[UIScreen mainScreen] bounds];
CGFloat viewWidth = applicationFrame.size.width / 2;
if(![MyUIResources isPhone]){
viewWidth = applicationFrame.size.width / 4;
}
[self.view addConstraints:#[
[NSLayoutConstraint constraintWithItem:self.optionsToCreateViewController.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:-(orgBB.frame.size.height + 75)],
[NSLayoutConstraint constraintWithItem:self.optionsToCreateViewController.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:-viewWidth]
]];
is this the right implementation of a view controller in another view controller
In Collection view cell:
[button addTarget:self action:#selector(buttonPressed:) forControlEvents: UIControlEventTouchUpInside];
self.contentView.userInteractionEnabled = FALSE;
self.userInteractionEnabled = TRUE;
I also put a break point to buttonPressed function, it's not hitting. i also used
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [self->button hitTest:[self->button convertPoint:point fromView:self] withEvent:event];
if (view == nil) {
view = [super hitTest:point withEvent:event];
}
return view;
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if ([super pointInside:point withEvent:event]) {
return YES;
}
return !self->button.hidden && [self->button pointInside:[self->button convertPoint:point fromView:self] withEvent:event];
}
but no use. Could anyone please help me here, I'm stuck here badly
CGRect applicationFrame = [[UIScreen mainScreen] bounds];
self.view = [UIView new];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
self.view.backgroundColor = [UIColor colorWhite];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
self.collection = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
self.collection.translatesAutoresizingMaskIntoConstraints = NO;
self.collection.backgroundColor = [UIColor colorWhite];
self.collection.contentInset = UIEdgeInsetsMake(0, 7.0, 0, 16.0);
[self.collection registerClass:[OptionsToCreateCollectionViewCell class] forCellWithReuseIdentifier: [OptionsToCreateCollectionViewCell cellIdentifier]];
[self.collection setDataSource:self]; // UICollectionViewDataSource
[self.collection setDelegate:self]; // UICollectionViewDelegate
[self.view addSubview:self.collection];
CGFloat viewWidth = applicationFrame.size.width;
CGFloat frameWidth = viewWidth;
if(![MyUIResources isPhone]){
frameWidth = viewWidth/2;
}
[self.view addConstraints:
#[
[NSLayoutConstraint constraintWithItem:self.collection attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:10],
[NSLayoutConstraint constraintWithItem:self.collection attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:20],
[NSLayoutConstraint constraintWithItem:self.collection attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:frameWidth],
[NSLayoutConstraint constraintWithItem:self.collection attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:114*2.5]
]
];
What is the button's superview? Add the button on the cell.contentView, not cell self.
1.check that button frame(if outside cell not working, then any view overlapping that button not working ) and user interaction enabled or not
Otherwise
2.check that cell frame and height(check if there inside of collection view)
Otherwise
3.collection view supper frame set correctly (collection view frame need inside of superview)
If Frame working correct so assign that clipsToBounds = YES
self.yourview.clipsToBounds = YES;
notes:past that button Action in collection didselecteditem . then check working or not because easily identified issue only that button or that class based.

Change Frame of Subview in Superview

I have a ViewController that adds an UIView, SpeciesImageView as a subview in viewDidLoad and set constraints in viewWillLayoutSubviews.
SpeciesImageView does not have a nib file. When we create speciesImageView in viewDidLoad, it calls initWithFrame in SpeciesImageView class.
This works fine (in both landscape and portrait) until the phone rotates. I tried setting the constraints as speciesImageView.frame.size.width, but that doesn't work because initWithFrame isn't called when the orientation changes, so the height/width of the speciesImageView remains unchanged.
On the other hand, using screenRect doesn't change the actual size of the UIView, it changes its size within the superview. So in other words, I haven't found a way to change the size of the actual speciesImageView on orientation change.
And for reasons lost to me, it gets completely messed up when you rotate it back to the original position.
- (void)viewDidLoad
{
self.tabBarController.tabBar.hidden=YES;
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.navigationController.navigationBar.hidden = NO;
//self.navigationController.navigationBar.translucent = YES;
UIImage *plantinfo;
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
plantinfo = [UIImage imageNamed:#"plantinfo_frame.png"];
} else {
plantinfo = [UIImage imageNamed:#"plantinfo.png"];
}
UIBarButtonItem *tempButton = [[UIBarButtonItem alloc] initWithImage:plantinfo
style:UIBarButtonItemStylePlain
target:self
action:#selector(toggleText:)];
self.navigationItem.rightBarButtonItem = tempButton;
[tempButton release];
self.title = theSpecies.scientificName;
//[self.navigationItem.backBarButtonItem setTitle:#""];
self.navigationItem.backBarButtonItem.title = #"";
infoViewSegmentedControl.backgroundColor = [UIColor blackColor];
webView.backgroundColor = [UIColor blackColor];
_activityIndicator.hidden = YES;
[webView setOpaque:YES];
webView.delegate = self;
// Do double justification
[webView loadHTMLString:[self formatHTML:theSpecies] baseURL:nil];
showingInfoView = NO;
//
// Resize containerView, infoview according to iphone 5 screen size.
//
infoView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
CGPoint screenOrigin = [[UIScreen mainScreen] bounds].origin;
CGSize viewSize = [[UIScreen mainScreen] bounds].size;
CGPoint origin = infoView.frame.origin;
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
infoView.frame = CGRectMake(screenOrigin.x,
screenOrigin.y + statusBarFrame.size.height,
viewSize.width,
viewSize.height - origin.y - statusBarFrame.size.height);
speciesImageView = [[SpeciesImageView alloc]
initWithFrame:CGRectMake(screenOrigin.x,
screenOrigin.y,
viewSize.width,
viewSize.height)];
} else {
infoView.frame = CGRectMake(screenOrigin.x,
screenOrigin.y,
viewSize.width,
viewSize.height - origin.y - statusBarFrame.size.height);
speciesImageView = [[SpeciesImageView alloc]
initWithFrame:CGRectMake(screenOrigin.x,
screenOrigin.y,
viewSize.width,
viewSize.height - statusBarFrame.size.height)];
}
speciesImageView.delegate = self;
[containerView addSubview:speciesImageView];
managedObjectContext = [(LeafletAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
[self parseImageURLArray];
}
-(void)viewWillLayoutSubviews{
if(speciesImageView.window != nil){
CGRect screenRect = [[UIScreen mainScreen] bounds];
speciesImageView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *widthConst = [NSLayoutConstraint
constraintWithItem:speciesImageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:screenRect.size.width];
NSLayoutConstraint *heightConst = [NSLayoutConstraint
constraintWithItem:speciesImageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:screenRect.size.height];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint
constraintWithItem:speciesImageView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint
constraintWithItem:speciesImageView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
[self.view addConstraints:#[widthConst, heightConst, bottomConstraint, rightConstraint]];
}
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
imageScrollView = [[UIScrollView alloc] initWithFrame:frame];
imageScrollView.delegate = self;
imageScrollView.backgroundColor = [UIColor blackColor];
[self addSubview:imageScrollView];
imageScrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *widthConst = [NSLayoutConstraint constraintWithItem:imageScrollView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:imageScrollView.frame.size.width];
NSLayoutConstraint *heightConst = [NSLayoutConstraint constraintWithItem:imageScrollView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:imageScrollView.frame.size.height];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint
constraintWithItem:imageScrollView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0.0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint
constraintWithItem:imageScrollView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[self addConstraints:#[widthConst, heightConst, bottomConstraint, rightConstraint]];
}
return self;
}
If you want your views to adjust to the size of the superview,
then you need something like this (set the margin to whatever you like):
CGFloat margin = 0;
NSString * visualFormatH = [NSString stringWithFormat:#"|-(%f)-[speciesImageView]-(%f)-|", margin, margin];
[self.containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualFormatH
options:0
metrics:Nil
views:#{#"speciesImageView": speciesImageView}]];
NSString * visualFormatV = [NSString stringWithFormat:#"V:|-(%f)-[speciesImageView]-(%f)-|", margin, margin];
[self.containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualFormatV
options:0
metrics:Nil
views:#{#"speciesImageView": speciesImageView}]];
Now speciesImageView will adjust its frame whenever the superview frame changes.
Here is a generic example:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView * sampleView = [UIView new];
sampleView.backgroundColor = [UIColor redColor];
sampleView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:sampleView];
CGFloat margin = 0;
NSString * visualFormatH = [NSString stringWithFormat:#"|-(%f)-[sampleView]-(%f)-|", margin, margin];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualFormatH
options:0
metrics:Nil
views:#{#"sampleView": sampleView}]];
NSString * visualFormatV = [NSString stringWithFormat:#"V:|-(%f)-[sampleView]-(%f)-|", margin, margin];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualFormatV
options:0
metrics:Nil
views:#{#"sampleView": sampleView}]];
}
Auto Layout Getting Started
Auto Layout Visual Format Documentation

How to add custom view with autolayout programmatically

My storyboard has a single view controller containing a UIScrollView. I want to add some custom view (each view will contain a image & label) to the scrollview programmatically in the viewController. I have set constraints to the UIScrollView in the storyboard but struggling to add custom views with constraints. How can i do this so that it can be appeared as same for all the devices (iPhone/iPad) ? I tried like below but it does not seems to be working.
In My ViewController.m -
#import "MainViewController.h"
#define DEFAULT_ROW_HEIGHT 50
#interface MainViewController ()
#property float topMarginFromUpperView;
#end
#implementation MainViewController
-(void) addCustomRowToView {
self.topMarginFromUpperView += DEFAULT_ROW_HEIGHT + 10.0f;
UIView *customRow = [[UIView alloc] initWithFrame:self.scrollView.bounds];
customRow.backgroundColor = [UIColor redColor];
[self.scrollView addSubview:customRow];
customRow.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTop multiplier:1.0f constant:self.topMarginFromUpperView]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:10.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-10.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeHeight multiplier:0.1f constant:0.0f]];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.topMarginFromUpperView = 0.0f;
for (NSInteger i =0; i< 10; i++) {
[self addCustomRowToView];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
EDITED :
Still struggling with constraint setting. App crashes due to invalid constraint. Tried as below -
-(void) setConstraints:(UIView *)customRow {
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTop multiplier:1.0f constant:self.topMarginFromSuperView]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:10.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-10.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:customRow attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeHeight multiplier:0.1f constant:0.0f]];
}
-(void) addCustomRowToView {
UIView *customRow = [[UIView alloc]initWithFrame:CGRectMake(DEFAULT_PADDING, self.topMarginFromSuperView, self.view.bounds.size.width - 2*DEFAULT_PADDING, DEFAULT_ROW_HEIGHT)];
customRow.backgroundColor = [UIColor redColor];
customRow.translatesAutoresizingMaskIntoConstraints = NO;
[self setConstraints:customRow];
[self.scrollView addSubview:customRow];
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, self.topMarginFromSuperView + DEFAULT_ROW_HEIGHT);
self.topMarginFromSuperView += DEFAULT_ROW_HEIGHT + DEFAULT_PADDING;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.topMarginFromSuperView = 0.0f;
//for (NSInteger i =0; i< 10; i++) {
[self addCustomRowToView];
// }
}
#end
Here is solution for your problem:
- (void)addCustomView
{
UIView *lastView;
for (int i = 0; i<10; i++)
{
UIView *view = [[UIView alloc] init];
view.backgroundColor = i%2 ? [UIColor redColor] : [UIColor greenColor];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:view];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[view]-10-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1 constant:-20.0]];
if (i == 0)
{
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view(40)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
}
else
{
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[lastView(40)]-10-[view(40)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(lastView,view)]];
}
lastView = view;
}
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[lastView(40)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(lastView)]];
}

Custom Transition Only Works First Time on Retina Simulator

I have a custom presentation using the UIViewControllerContextTransitioning protocol that splits the view of the initial controller in half (actually an image of that view), and slides the two haves off the screen to the left and right. The animation works properly for the presentation and dismissal the first time it is carried out, but is wrong on any subsequent presentations.
Specifically, the image views that hold the half images of the view don't respect the constraints they are given, and so are twice the size they should be (they're the size dictated by the retina image). This only happens on simulators with retina displays.
The animator object is deallocated after each presentation and dismissal, so it's not at all clear why it should behave differently on subsequent runs.
Here is the code, including the logs that show the size of one of the image views both before and after the constraints are applied,
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context {
self.ctxContainerView = [context containerView];
UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;
UIView *fromView = [context viewControllerForKey:UITransitionContextFromViewControllerKey].view;
// Create image from view
UIView *viewToImage = (self.isPresenting)? fromView : toView;
UIGraphicsBeginImageContextWithOptions(viewToImage.bounds.size, viewToImage.opaque, 0);
[viewToImage.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Create left and right haves of the view image
CGImageRef tmp = CGImageCreateWithImageInRect([img CGImage], CGRectMake(0, 0, img.size.width * img.scale*0.5, img.size.height * img.scale));
UIImage *imgLeft = [UIImage imageWithCGImage:tmp];
CGImageRef tmp2 = CGImageCreateWithImageInRect([img CGImage], CGRectMake(img.size.width * img.scale*0.5, 0, img.size.width * img.scale*0.5, img.size.height * img.scale));
UIImage *imgRight = [UIImage imageWithCGImage:tmp2];
CGImageRelease(tmp);
CGImageRelease(tmp2);
self.snapshotViewLeft = [[UIImageView alloc] initWithImage:imgLeft];
self.snapshotViewRight = [[UIImageView alloc] initWithImage:imgRight];
NSLog(#"Before constraints %#", NSStringFromCGSize(self.snapshotViewLeft.frame.size)); //prints {375, 1334} all the time
if (self.isPresenting) {
[self.ctxContainerView insertSubview:toView belowSubview:fromView];
[self.ctxContainerView addSubview:self.snapshotViewLeft];
[self.ctxContainerView addConstraints: [self setupConstraintsForLeftView:self.snapshotViewLeft]];
[self.ctxContainerView addSubview:self.snapshotViewRight];
[self.ctxContainerView addConstraints: [self setupConstraintsForRightView:self.snapshotViewRight]];
[self.ctxContainerView layoutIfNeeded];
NSLog(#"After constraints %#", NSStringFromCGSize(self.snapshotViewLeft.frame.size)); // prints {187.5, 667} the first time (which is the correct size), but {375, 1334} the second time the controller is presented
// Remove the source view, and animate out the two image views
[fromView removeFromSuperview];
self.leftCon.constant = -self.ctxContainerView.frame.size.width/2;
self.rightCon.constant = self.ctxContainerView.frame.size.width/2;
[UIView animateWithDuration:ANIMATION_TIME animations:^{
[self.ctxContainerView layoutIfNeeded];
} completion:^(BOOL finished) {
[self.snapshotViewRight removeFromSuperview];
[self.snapshotViewLeft removeFromSuperview];
[context completeTransition:YES];
}];
}else{
[self.ctxContainerView addSubview:self.snapshotViewLeft];
[self.ctxContainerView addConstraints: [self setupConstraintsForLeftView:self.snapshotViewLeft]];
self.leftCon.constant = -self.ctxContainerView.frame.size.width/2;
[self.ctxContainerView addSubview:self.snapshotViewRight];
[self.ctxContainerView addConstraints: [self setupConstraintsForRightView:self.snapshotViewRight]];
self.rightCon.constant = self.ctxContainerView.frame.size.width/2;
[self.ctxContainerView layoutIfNeeded];
// Animate in the two image views
self.leftCon.constant = 0;
self.rightCon.constant = 0;
[UIView animateWithDuration:ANIMATION_TIME animations:^{
[self.ctxContainerView layoutIfNeeded];
} completion:^(BOOL finished) {
[self.snapshotViewRight removeFromSuperview];
[self.snapshotViewLeft removeFromSuperview];
[fromView removeFromSuperview];
[context completeTransition:YES];
}];
}
}
-(NSArray *)setupConstraintsForLeftView:(UIView *) inView {
inView.translatesAutoresizingMaskIntoConstraints = NO;
self.leftCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeLeft relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0];
NSArray *arr = #[self.leftCon,con1,con2,con3];
return arr;
}
-(NSArray *)setupConstraintsForRightView:(UIView *) inView {
inView.translatesAutoresizingMaskIntoConstraints = NO;
self.rightCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeRight multiplier:1 constant:0];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0];
NSArray *arr = #[self.rightCon,con1,con2,con3];
return arr;
}
The question is, why is the behavior different for runs after the first, even though the system should be back to the same start state after the first presentation and dismissal?

iOS Auto layout Rows

I'm trying to understand auto layout better and I created some static methods that can help me out. Now I'm trying to setup an array of views in like a tableview structure (in rows). This works kinda.
See here my problem:
To achieve this I use this code:
//In the update constraints method
UIView *view1 = [self createView];
view1.backgroundColor = [UIColor redColor];
UIView *view2 = [self createView];
view2.backgroundColor = [UIColor yellowColor];
[self addSubview:view1];
[self addSubview:view2];
[self addConstraints:[DXVFL row:#[view1, view2] inView:self]];
//The row method
+ (NSArray *) row:(NSArray const *)views inView:(UIView const *)superview {
NSMutableArray *constraints = [NSMutableArray new];
CGFloat multiplier = 1.f / [views count];
UIView *prev = nil;
for (UIView *view in views) {
if (!view.hidden) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeWidth multiplier:1.f constant:0.f]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeHeight multiplier:multiplier constant:0.f]];
if (prev) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:prev attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
}
prev = view;
}
}
return [constraints copy];
}
So this part works (I guess). But when I want to split the yellow view again in rows. The views are always added into the top region?
In the update constraints method I do this:
UIView *view1 = [self createView];
view1.backgroundColor = [UIColor redColor];
UIView *view2 = [self createView];
view2.backgroundColor = [UIColor yellowColor];
[self addSubview:view1];
[self addSubview:view2];
UIView *view3 = [self createView];
UIView *view4 = [self createView];
[view2 addSubview:view3];
[view2 addSubview:view4];
[self addConstraints:[DXVFL row:#[view1, view2] inView:self]];
[view2 addConstraints:[DXVFL row:#[view3, view4] inView:view2]];
But I want those two rows in the bottom? What am I doing wrong?
Off the top of my head:
You need to add a constraint to tell it to align the view to the top of the superview. At the moment, you just set the height and width but you don't tell it where to put its top edge.
Try changing your code to this:
if (!prev) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]];
} else {
[constraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:prev attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
}

Resources