I would like to drag one UIView, and have it move the position of other views attached to it as if they were all attached via string. I added two attached behaviours, one between my views and one between view which allowed to dragging and anchor point. And it works. But my problem is that my views continue to move even after gesture ended. My current result:
What do i do wrong?
Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.views = [[NSMutableArray alloc] init];
self.v2 = [[UIView alloc] initWithFrame:CGRectMake(50, 450, 200, 200)];
self.v2.backgroundColor = [UIColor colorWithRed:34.0f/255.0f green:167.0f/255.0f blue:240.0f/255.0f alpha:1.0];
[self.view addSubview:self.v2];
self.v1 = [[UIView alloc] initWithFrame:CGRectMake(150, 450, 200, 200)];
self.v1.backgroundColor = [UIColor colorWithRed:210.0f/255.0f green:82.0f/255.0f blue:127.0f/255.0f alpha:1.0];
[self.view addSubview:self.v1];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.gravity = [[UIGravityBehavior alloc]initWithItems:#[self.v1, self.v2]];
self.gravity.gravityDirection = CGVectorMake(0.0, 0.0);
[_animator addBehavior:_gravity];
_collision = [[UICollisionBehavior alloc]initWithItems:#[self.v1]];
_collision.collisionDelegate = self;
_collision.translatesReferenceBoundsIntoBoundary = YES;
[_animator addBehavior:_collision];
UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:#[self.v1]];
itemBehaviour.elasticity = 0.6;
itemBehaviour.allowsRotation = NO;
[_animator addBehavior:itemBehaviour];
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.v1 addGestureRecognizer:gesture];
UIAttachmentBehavior *attach = [[UIAttachmentBehavior alloc] initWithItem:self.v2 attachedToItem:self.v1];
attach.damping = 0.50f;
attach.length = 150.0f;
attach.frequency = 1.9;
[_animator addBehavior:attach];
}
-(void)handlePan:(UIPanGestureRecognizer *)gesture
{
CGPoint touchPoint = [gesture locationInView:self.view];
static float y ;
if (gesture.state == UIGestureRecognizerStateBegan) {
self.attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.v1 attachedToAnchor:touchPoint];
self.attachmentBehavior.damping = 0.50f;
self.attachmentBehavior.frequency = 1.9;
[_animator addBehavior:self.attachmentBehavior];
y = self.v1.center.y;
} else if (gesture.state == UIGestureRecognizerStateChanged ) {
CGPoint center = self.v1.center;
center.x = touchPoint.x;
center.y = y;
self.attachmentBehavior.anchorPoint = center;
} else if (gesture.state == UIGestureRecognizerStateEnded) {
[self.animator removeBehavior:self.attachmentBehavior];
}
}
Related
This is a custom UIView subclass for a tableHeaderView.
I'm currently trying to put a gradient to an UILabel that's in a vertical UIStackView. It works really well without gradient even in RTL languages (gets moved to the right), but as soon as I add the gradient, the label that's the maskView of the gradientView gets completely out of its place. Here the code for better comprehension:
- (instancetype)initWithTitle:(NSString *)title subtitle:(NSString *)subtitle colors:(NSArray<UIColor *> *)colors {
if (self = [super initWithFrame:CGRectMake(0, 0, self.superview.frame.size.width, 200)]) {
// Labels
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = title;
titleLabel.font = [UIFont boldSystemFontOfSize:42.f];
titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[titleLabel sizeToFit];
UILabel *subtitleLabel = [[UILabel alloc] init];
subtitleLabel.text = subtitle;
subtitleLabel.font = [UIFont systemFontOfSize:24.f];
subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[subtitleLabel sizeToFit];
// Create gradient
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = titleLabel.bounds;
NSMutableArray *cgcolors = [NSMutableArray new];
for (int i = 0; i < colors.count; i++) {
cgcolors[i] = (id)colors[i].CGColor;
}
gradient.colors = cgcolors;
gradient.locations = #[#0, #1];
UIView *gradientView = [[UIView alloc] initWithFrame:titleLabel.bounds];
[gradientView.layer addSublayer:gradient];
[gradientView addSubview:titleLabel];
gradientView.maskView = titleLabel;
// Stack
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:#[gradientView, subtitleLabel]];
stackView.alignment = UIStackViewAlignmentLeading;
stackView.axis = UILayoutConstraintAxisVertical;
stackView.distribution = UIStackViewDistributionEqualCentering;
stackView.spacing = 0;
stackView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:stackView];
[NSLayoutConstraint activateConstraints:#[
[stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:25],
[stackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
[stackView.heightAnchor constraintEqualToConstant:titleLabel.frame.size.height + subtitleLabel.frame.size.height]
]];
}
return self;
}
With LTR languages, it looks good, but with RTL languages, the frame of the gradientView that contains the titleLabel becomes {[width of subtitleLabel], 0, 0, 0} for no reason. The cause is the gradient view, because without it it works fine, but I can't find why.
I tried titleLabel.layer.mask, placing the gradient block at the end, but nothing worked.
EDIT :
Working code:
- (instancetype)initWithTitle:(NSString *)title subtitle:(NSString *)subtitle colors:(NSArray<__kindof UIColor *> *)colors {
if (self = [super initWithFrame:CGRectMake(0, 0, self.superview.frame.size.width, 200)]) {
// Labels
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = title;
titleLabel.font = [UIFont boldSystemFontOfSize:42.f];
titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[titleLabel sizeToFit];
UILabel *subtitleLabel = [[UILabel alloc] init];
subtitleLabel.text = subtitle;
subtitleLabel.font = [UIFont systemFontOfSize:24.f];
subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[subtitleLabel sizeToFit];
// Create gradient
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = titleLabel.bounds;
NSMutableArray *cgcolors = [NSMutableArray new];
for (int i = 0; i < colors.count; i++) {
cgcolors[i] = (id)colors[i].CGColor;
}
gradient.colors = cgcolors;
gradient.locations = #[#0, #1];
UIView *gradientView = [[UIView alloc] init];
[gradientView.layer addSublayer:gradient];
gradientView.maskView = titleLabel;
// Stack
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:#[gradientView, subtitleLabel]];
stackView.alignment = UIStackViewAlignmentLeading;
stackView.axis = UILayoutConstraintAxisVertical;
stackView.distribution = UIStackViewDistributionEqualCentering;
stackView.spacing = 0;
stackView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:stackView];
[NSLayoutConstraint activateConstraints:#[
[stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:25],
[stackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
[stackView.heightAnchor constraintEqualToConstant:titleLabel.frame.size.height + subtitleLabel.frame.size.height],
[gradientView.widthAnchor constraintEqualToConstant:titleLabel.frame.size.width],
[gradientView.heightAnchor constraintEqualToConstant:titleLabel.frame.size.height]
]];
}
return self;
}
The problem is that your gradientView has no intrinsic content size.
Try changing your constraints to this:
[NSLayoutConstraint activateConstraints:#[
[stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:25],
[stackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
// shouldn't need to set a height anchor on the stackView
//[stackView.heightAnchor constraintEqualToConstant:titleLabel.frame.size.height + subtitleLabel.frame.size.height],
[gradientView.widthAnchor constraintEqualToConstant:titleLabel.frame.size.width],
[gradientView.heightAnchor constraintEqualToConstant:titleLabel.frame.size.height],
]];
need to make effect, that text is interrupted by another text/uiimageview, but cannot understand how does it works. For example i need to make interface similar to ios7 status bar where operator name such a "Oper ..." + icon + time. So i cannot do this right way
operatorName = [self getOperatorName];
UILabel *operatorLabel = [[UILabel alloc] init];
operatorLabel.backgroundColor = [UIColor clearColor];
operatorLabel.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:kStatusBarFontOperatorSize];
operatorLabel.textColor = kStatusBarTextColor;
operatorLabel.shadowOffset = CGSizeMake(0.f, -0.6);
operatorLabel.shadowColor = kStatusBarTextShadow;
operatorLabel.adjustsFontSizeToFitWidth = YES;
operatorLabel.text = operatorName;
operatorLabel.frame = CGRectMake(0.0, 0.0, operatorStrSize.width, operatorStrSize.height);
[operatorLabel sizeToFit];
/* connection type */
UIImageView *conImgView = [[UIImageView alloc] initWithImage:conImg];
/* time in status bar */
time = [self getStatusBarTime];
UILabel *statusBarLabel = [[UILabel alloc] init];
statusBarLabel.font = [UIFont fontWithName:#"HelveticaNeue-Medium" size:kStatusBarFontSize];
statusBarLabel.textColor = kStatusBarTextColor;
statusBarLabel.adjustsFontSizeToFitWidth = YES;
statusBarLabel.text = time;
int maxDistance = imgView.frame.size.width/2 - timeStrSize.width/2;
int connectionPower = 44;
double delimiter = 0;
NSString *cName = [self returnChoosenConnectionName];
if ([cName isEqualToString:#"Wi-Fi"]) {
delimiter = 6.5;
} else {
delimiter = 9.5;
}
int fullLine = connectionPower + operatorLabel.frame.size.width + delimiter + conImgView.frame.size.width;
if (fullLine > maxDistance) {
// need to interrupt text in operator name, but how ?
} else {
// all good placed
x = 44.0;
operatorLabel.frame = CGRectMake(x, 3.0, operatorStrSize.width , operatorStrSize.height);
[operatorLabel sizeToFit];
NSString *cName = [self returnChoosenConnectionName];
if ([cName isEqualToString:#"Wi-Fi"]) {
x += operatorLabel.frame.size.width + 6.5;
} else {
x += operatorLabel.frame.size.width + 9.5;
}
conImgView.frame = CGRectMake(x, 0.0, conImgView.frame.size.width, conImgView.frame.size.height);
[imgView addSubview:operatorLabel];
[imgView addSubview:conImgView];
}
statusBarLabel.frame = CGRectMake(imgView.frame.size.width/2 - timeStrSize.width/2, 2.5, timeStrSize.width , timeStrSize.height);
[imgView addSubview:statusBarLabel];
What i need:
what i have:
I would suggest to use autolayout rather than trying to calculate everything yourself. You may use XIB and set constraints in there or you may do everything programmatically. Here is a sample code that will get you started. It creates controls and sets constraints programatically; it doesn't use XIB.
- (void)viewDidLoad
{
[super viewDidLoad];
/*
Uncomment this if you would like to translate your subviews vertically.
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 40, self.view.frame.size.width, 50)];
[self.view addSubview:view];
UIView *superview = view;
*/
UIView *superview = self.view;
UILabel *label1 = [[UILabel alloc] init];
label1.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:14.0];
label1.text = #"MTS";
[label1 sizeToFit];
[label1 setTranslatesAutoresizingMaskIntoConstraints:NO];
UILabel *label2 = [[UILabel alloc] init];
label2.font = [UIFont fontWithName:#"HelveticaNeue-Medium" size:14.0];
label2.text = #"1:22 PM";
[label2 sizeToFit];
[label2 setTranslatesAutoresizingMaskIntoConstraints:NO];
UIImageView *image1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"strength.png"]];
UIImageView *image2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"carrier.png"]];
[image1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[image2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[image1 sizeToFit];
[image2 sizeToFit];
[superview addSubview:image1];
[superview addSubview:label1];
[superview addSubview:image2];
[superview addSubview:label2];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(image1, image2, label1, label2);
NSString *format = [NSString stringWithFormat:#"|-[image1(<=%f)]-[label1]-[image2(<=%f)]-[label2(>=20)]-|",
image1.frame.size.width, image2.frame.size.width];
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:format
options:NSLayoutFormatAlignAllCenterY
metrics:nil
views:viewsDictionary];
[self.view addConstraints:constraints];
}
When adding views to a layout in code iOS will attempt to convert the autosizing mask for that view to auto layout constraints. Those auto-generated constraints will conflict with any constraints added within the application code. It is essential to turn the translation off:
setTranslatesAutoresizingMaskIntoConstraints:NO
The result of the code is:
I have 2 imageviews that I want to be able to move. This is my code:
panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(gestureRecognizerMethod:)];
imageview = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"]];
imageview.frame = CGRectMake(390, 100, 80, 16);
imageview.userInteractionEnabled = YES;
[imageview addGestureRecognizer:panRecognizer];
[self addSubview:imageview];
imageview2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"]];
imageview2.frame = CGRectMake(390, 120, 80, 17);
imageview2.userInteractionEnabled = YES;
[imageview2 addGestureRecognizer:panRecognizer];
[self imageview2];
And in my gestureRecognizer method:
- (void)gestureRecognizerMethod:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.view == imageview)
{
if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint startPoint = [recognizer locationInView:self];
imageview.center = startPoint;
}
}
if (recognizer.view == imageview2)
{
if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint startPoint = [recognizer locationInView:self];
imageview2.center = startPoint;
}
}
}
I'm only able to move one of the view. What is wrong?
Create a new gesture recognizer for the second image view. You can use the same target and action for both recognisers but you can only attach any one recogniser to a single view.
Try this:
ResizeImage on github
I have a class which is predefining some labels and binding their values in a UIScrollView.
I've managed to show those labels, but now I'm stuck at putting a label at the 2nd part of the ScrollView.
I've pushed my project to gitHub.
I can change the label's place on the already visible part, but I must be overlooking something.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.title = _detail.name;
UIColor *bgColor = [UIColor blackColor];
UIColor *txtColor = [UIColor grayColor];
CGRect frame;
frame.origin.x = 0;
frame.origin.y = 0;
frame.size.width = _scrollView.frame.size.width *2;
NSString *phoneNr = (_detail.phoneNr == nil) ? #"Not specified" : _detail.phoneNr;
_telLabel = [self prepareLabel:phoneNr textColor:txtColor bgColor:bgColor page:0 y:telNrYAxis];
_webLabel = [self prepareLabel:#"Visit website" textColor:txtColor bgColor:bgColor page:0 y:websiteYAxis];
_detail.address = [_detail.address stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"\n\t "]];
NSArray *addressArrComponents = [_detail.address componentsSeparatedByString:#","] ;
_addressLabel = [self prepareLabel:[addressArrComponents componentsJoinedByString:#"\n"] textColor:txtColor bgColor:bgColor page:0 y:addressYAxis];
UILabel *lbl = [self prepareLabel:#"Derp" textColor:txtColor bgColor:bgColor page:1 y:0];
_detailView = [[UIView alloc] initWithFrame:frame];
_detailView.backgroundColor = [UIColor blackColor];
[_detailView addSubview:_webLabel];
[_detailView addSubview:_addressLabel];
[_detailView addSubview:_telLabel];
[_detailView addSubview:lbl];
[_scrollView addSubview:_detailView];
NSLog(#"%f",self.view.frame.size.height - (_scrollView.frame.origin.y + _scrollView.frame.size.height) );
_pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height - 250 , self.view.frame.size.width/4, 120)];
_pageControl.numberOfPages=2;
_pageControl.currentPage=0;
[_pageControl addTarget:self action:#selector(pageChange:) forControlEvents:UIControlEventTouchDown];
_scrollView.contentSize = CGSizeMake(800,800);
_scrollView.delegate=self;
_scrollView.backgroundColor = [UIColor blackColor];
_scrollView.pagingEnabled=YES;
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.scrollsToTop = NO;
[self pageChange:0];
[self.view addSubview:_pageControl];
// Do any additional setup after loading the view.
}
-(UILabel*)prepareLabel:(NSString*) text textColor:(UIColor*)textColor bgColor:(UIColor*)backgroundColor page:(int)page y:(int) yPos{
int lines = [[text componentsSeparatedByString:#"\n"] count];
CGRect labelFrame = CGRectMake(_detailView.frame.size.width * page +20,yPos,self.view.frame.size.width, [UIFont systemFontSize]*lines);
UILabel *returnLabel = [[UILabel alloc] initWithFrame:labelFrame];
returnLabel.text = text;
returnLabel.backgroundColor = backgroundColor;
returnLabel.textColor = textColor;
[returnLabel setNumberOfLines:lines];
[returnLabel sizeToFit];
return returnLabel;
}
- (void)loadScrollViewWithPage:(int)page {
NSLog(#"Derped");
}
-(IBAction)pageChange:(id)sender{
int page=_pageControl.currentPage;
CGRect frame = _scrollView.frame;
frame.origin.x = _scrollView.frame.size.width * page;
frame.origin.y = 0;
//CGRect frame= (page == 0) ? _detailFrame : _reviewFrame;
NSLog(#"%f",frame.origin.x);
[_scrollView scrollRectToVisible:frame animated:YES];
}
The delegate -(IBAction)pageChange:(id)sender gets fired, but I must be doing something wrong with the frames somewhere :s
Please take a look!
Try to implement this method may help you :
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = self.scrollView.frame.size.width;
float fractionalPage = self.scrollView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
self.pageControl.currentPage = page;
}
I am having multiple Imageview's where I am dividing the 360 degrees/no.of.Imageviews to get an angle to rotate each Imageview.
#define angle ((2*M_PI)/13.0)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
mainView.backgroundColor = [UIColor blackColor];
for(currentIndex = 0; currentIndex < 13; currentIndex++)
{
UIImageView *imageView = [self getCardsWithAngle:(currentIndex*angle)];
[mainView addSubview:imageView];
}
}
-(UIImageView *)getCardsWithAngle:(float)aAngle
{
CGRect frame = CGRectMake(mainView.center.x+50, mainView.center.y-100, 250,200);
CGPoint anchorPoint = CGPointMake(0.5,1.5);
imgView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"card_1.png"]];
imgView.frame = frame;
imgView.backgroundColor = [UIColor clearColor];
if(aAngle == 0.0)
{
imgView.layer.anchorPoint = anchorPoint;
return imgView;
}
imgView.layer.anchorPoint = anchorPoint;
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat:aAngle];
rotationAnimation.duration = 3.0;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.delegate = self;
[rotationAnimation setValue:[NSNumber numberWithFloat:aAngle] forKey:#"finalAngle"];
[imgView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
return imgView;
}
Here My problem is all the imageviews start animating at a time. I want to rotate the image views to an angle one after another.
Any ideas will be appreciated,
Thanks all