How to bind a vertical constraint programatically to a view? - ios

I am reading into implementing iAd into my application.
Apple has provided a basic banner sample code.
I have a problem porting the solution to my UITableViewController. Since I don't use any xib file in order to bind the introduced NSLayoutConstraint to my tableview:
#property (nonatomic, strong) IBOutlet NSLayoutConstraint *bottomConstraint;
If this isn't bound properly, I wont be able to shrink the view according to the ad's size:
- (void)layoutAnimated:(BOOL)animated
{
CGRect contentFrame = self.view.bounds;
// all we need to do is ask the banner for a size that fits into the layout area we are using
CGSize sizeForBanner = [_bannerView sizeThatFits:contentFrame.size];
// compute the ad banner frame
CGRect bannerFrame = _bannerView.frame;
if (_bannerView.bannerLoaded) {
// bring the ad into view
contentFrame.size.height -= sizeForBanner.height; // shrink down content frame to fit the banner below it
bannerFrame.origin.y = contentFrame.size.height;
bannerFrame.size.height = sizeForBanner.height;
bannerFrame.size.width = sizeForBanner.width;
// if the ad is available and loaded, shrink down the content frame to fit the banner below it,
// we do this by modifying the vertical bottom constraint constant to equal the banner's height
//
NSLayoutConstraint *verticalBottomConstraint = self.bottomConstraint;
verticalBottomConstraint.constant = sizeForBanner.height;
[self.view layoutSubviews];
} else {
// hide the banner off screen further off the bottom
bannerFrame.origin.y = contentFrame.size.height;
}
[UIView animateWithDuration:animated ? 0.25 : 0.0 animations:^{
_contentView.frame = contentFrame;
[_contentView layoutIfNeeded];
_bannerView.frame = bannerFrame;
}];
}
The question is how do I bind programatically the bottomConstraint to the vertical bottom of my view in order to shrink it afterwards?

Related

Autolayout with dynamic height

I am trying to create a subclass of UIView that will show a list of fixed sized UIImages similar to how a UILabel displays letters.If all the images won't fit on one line, the images are arranged on multiple lines.
How can I achieve this using autolayouts so that if I put this view in a UIStackView the images will be listed correctly?
Here is a sample if I did it using fixed position :
- (void) layoutSubviews{
[super layoutSubviews];
CGRect bounds = self.bounds;
CGRect imageRect = CGRectMake(0, 0, 30, 30);
for (UIImageView* imageView in self.imageViews){
imageView.frame = imageRect;
imageRect.origin.x = CGRectGetMaxX(imageRect);
if (CGRectGetMaxX(imageRect) > CGRectGetMaxX(bounds)){
imageRect.origin.x = 0.0;
imageRect.origin.y = CGRectGetMaxY(imageRect);
}
}
}
Update:
Here is a sample project to show the issue.
https://github.com/datinc/DATDemoImageListView
Here the link for ImageListView
https://github.com/datinc/DATDemoImageListView/blob/master/DATDemoImageListView/DATDemoImageListView.m
You should overwrite intrinsicContentSize returning the preferred content size of your view.
The problem is that in intrinsicContentSize the view has not it's final bounds. You can use an internal height constraint, and overwrite updateConstraints:
- (void)updateConstraints {
CGFloat theWidth = CGRectGetWidth(self.bounds);
NSUInteger theCount = [self.subviews count];
CGFloat theRows = ceilf(theCount / floorf(theWidth / 30.0));
self.heightConstraint.constant = 30.0 * theRows;
[super updateConstraints];
}
In viewDidAppear: and on layout changes (e. g. rotation) you have to call setNeedsUpdateConstraints to get a proper initial layout of the image views.

iAd frame returning wrong height on iPhone

I have an iAd which I am trying to position at the bottom of the screen though when I try to get the height of the ad so that I can put it at the bottom. This error only occurs when I enter the screen in a landscape orientation as the ad is taller in the portrait orientation and only on the iPhone simulator as on an ipad the ads are the same height.
The method I am using to put the add at the bottom is by setting the y value of the ad frame to the height of the view minus the height of the ad.
Here is the code that I am currently using:
- (void)viewWillAppear:(BOOL)animated
{
CGRect adFrame = banner.frame;
adFrame.origin.y = screenHeight - banner.frame.size.height;
adFrame.size.width = screenWidth;
banner.frame = adFrame;
[banner setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
}
- (void)viewDidLoad
{
[super viewDidLoad];
banner.frame = CGRectZero;
}
Any help would be much appreciated.
I would take a look at Apple's sample code
for integrating iAD exactly how you describe, particularly in ContainerBanner/ContainerBanner/BannerViewController.m. What you are looking for is ADBannerView's - (CGSize)sizeThatFits:(CGSize)size.
CGRect adRect = CGRectZero;
CGRect contentFrame = self.view.bounds;
adRect.size = [banner sizeThatFits:contentFrame.size];
adRect.origin.y = CGRectGetHeight(contentFrame) - CGRectGetHeight(adRect);
banner.frame = adRect;

Resize UItextview according to it's content in iOS7

I'm trying to resize a text view according to content & also it's sibling and parent container.
Below code is working fine in iOS 6
if (/* less than ios 7 */) {
CGRect frame = _textView.frame;
CGSize conSize = _textView.contentSize;
CGFloat difference = conSize.height - frame.size.height;
frame.size.height += difference;
_textView.frame = frame;
UIScrollView *parentView = (UIScrollView *)_textView.superview;
// adjust views residing below this text view.
// sibling view
UIView *belowView = // access it somehow
CGRect frame1 = belowView.frame;
frame1.origin.y += difference;
belowView.frame = frame1;
// adjust parent scroll view, increase height.
CGSize frame3 = parentView.contentSize;
frame3.height += difference;
parentView.contentSize = frame3;
} else {
// tried
[_textView sizeToFit];
[_textView layoutIfNeeded];
[parentView sizeToFit];
[parentView layoutIfNeeded];
}
Tried to follow iOS 7 solution from:
How do I size a UITextView to its content on iOS 7?
but not working.
Any pointers?
Working code solution from #NSBouzouki
if (/* ios 7 */) {
[_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
[_textView layoutIfNeeded];
}
CGRect frame = _textView.frame;
CGSize conSize = _textView.contentSize;
CGFloat difference = conSize.height - frame.size.height;
frame.size.height += difference;
_textView.frame = frame;
UIScrollView *parentView = (UIScrollView *)_textView.superview;
// adjust views residing below this text view.
// sibling view
UIView *belowView = // access it somehow
CGRect frame1 = belowView.frame;
frame1.origin.y += difference;
belowView.frame = frame1;
// adjust parent scroll view, increase height.
CGSize frame3 = parentView.contentSize;
frame3.height += difference;
parentView.contentSize = frame3;
It seems UITextView's contentSize property is not correctly set in iOS 7 till viewDidAppear:. This is probably because NSLayoutManager lays out the text lazily and the entire text must be laid out for contentSize to be correct. The ensureLayoutForTextContainer: method forces layout of the provided text container after which usedRectForTextContainer: can be used for getting the bounds. In order to get total width and height correctly, textContainerInset property must be taken into account. The following method worked for me.
- (CGRect)contentSizeRectForTextView:(UITextView *)textView
{
[textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];
CGRect textBounds = [textView.layoutManager usedRectForTextContainer:textView.textContainer];
CGFloat width = (CGFloat)ceil(textBounds.size.width + textView.textContainerInset.left + textView.textContainerInset.right);
CGFloat height = (CGFloat)ceil(textBounds.size.height + textView.textContainerInset.top + textView.textContainerInset.bottom);
return CGRectMake(0, 0, width, height);
}
Additionally, it seems UITextView's setContentSize: method is called from layoutSubviews. So, calling layoutIfNeeded on a textView (which itself calls layoutSubviews) after calling ensureLayoutForTextContainer: on its layoutManager, should make the textView's contentSize correct.
[someTextView.layoutManager ensureLayoutForTextContainer:someTextView.textContainer];
[someTextView layoutIfNeeded];
// someTextView.contentSize should now have correct value
GrowingTextViewHandler is an NSObject subclass which resizes text view as user types text.
Here is how you can use it.
#interface ViewController ()<UITextViewDelegate>
#property (weak, nonatomic) IBOutlet UITextView *textView;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
#property (strong, nonatomic) GrowingTextViewHandler *handler;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.handler = [[GrowingTextViewHandler alloc]initWithTextView:self.textView withHeightConstraint:self.heightConstraint];
[self.handler updateMinimumNumberOfLines:3 andMaximumNumberOfLine:8];
}
- (void)textViewDidChange:(UITextView *)textView {
[self.handler resizeTextViewWithAnimation:YES];
}
#end

UIScrollview - how to make subview smaller as it scrolls off screen

I'm a beginner with iOS, so i'm just not sure what to research here. I have a UIScrollView with a few square subViews added. How can i make the subviews smaller as they scroll off screen and bigger as they approach the center of the screen?
#import "HorizontalScrollMenuViewController.h"
#import <UIKit/UIKit.h>
#define SUBVIEW_WIDTH_HEIGHT 280
#interface HorizontalScrollMenuViewController : UIViewController
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#end
#implementation HorizontalScrollMenuViewController
-(void)viewDidLoad{
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor greenColor],[UIColor redColor],[UIColor orangeColor],[UIColor blueColor],nil ];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
CGFloat originX = (screenWidth - SUBVIEW_WIDTH_HEIGHT)/2.0; // get margin to left and right of subview
CGFloat originY = ((screenHeight - SUBVIEW_WIDTH_HEIGHT)/2);
// add subviews of all activities
for (int i = 0; i < colors.count; i++){
CGRect frame = CGRectMake(0,0,SUBVIEW_WIDTH_HEIGHT,SUBVIEW_WIDTH_HEIGHT);
frame.origin.x = self.scrollView.frame.size.width * i + originX;
frame.origin.y = originY;
UIView *subView = [[UIView alloc] initWithFrame:frame];
[UIView setAnimationBeginsFromCurrentState: YES];
subView.layer.cornerRadius = 15;
subView.layer.masksToBounds = YES;
subView.backgroundColor = [colors objectAtIndex:i];
[self.scrollView addSubview:subView];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * colors.count, self.scrollView.frame.size.height);
}
#end
Here you can find a fully working example of what you're trying to accomplish. It only has
one subview because it's just to give you an idea of how can you accomplish it. Also, this example was tested on an iPad (iOS7) simulator.
The *.h file
#import <UIKit/UIKit.h>
// Remember to declare ourselves as the scroll view delegate
#interface TSViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, strong) UIView *squareView;
#end
The *.m file
#import "TSViewController.h"
#implementation TSViewController
#synthesize squareView = _squareView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Create and configure the scroll view (light gray)
UIScrollView *myScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(100, 100, 500, 500)];
CGRect contentSize = myScrollView.frame;
contentSize.size.height = contentSize.size.height + 400;
myScrollView.contentSize = contentSize.size;
myScrollView.userInteractionEnabled = YES;
// give the scroll view a gray color so it's easily identifiable
myScrollView.backgroundColor = [UIColor lightGrayColor];
// remember to set yourself as the delegate of the scroll view
myScrollView.delegate = self;
[self.view addSubview:myScrollView];
// Create and configure the square view (blue)
self.squareView = [[UIView alloc] initWithFrame:CGRectMake(200, 400, 60, 60)];
self.squareView.backgroundColor = [UIColor blueColor];
[myScrollView addSubview:self.squareView];
}
// Here is where all the work happens
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Get the difference between the contentOffset y position and the squareView y position
CGFloat y = self.squareView.frame.origin.y - scrollView.contentOffset.y;
// If the square has gone out of view, return
if (y <= 0) {
return;
}
// Modify the squareView's frame depending on it's current position
CGRect squareViewFrame = self.squareView.frame;
squareViewFrame.size.height = y + 5;
squareViewFrame.size.width = y + 5;
squareViewFrame.origin.x = (scrollView.contentSize.width - squareViewFrame.size.width) / 2.0;
self.squareView.frame = squareViewFrame;
}
#end
And here is a little explanation of what is going on:
A UIScrollView has several properties that allow you to configure it correctly. For example it has a frame (gray) which is inherited from UIView; with this property you specify the visible size of the scroll view. It also has the contentSize (red) which specifies the total size of the scroll view; in the image it's showed as the red area but this is only for illustration purposes as it will not be visible in the program. Imagine the scroll view's frame as the window that let's you see only a part of the bigger content the scroll view has.
When the user starts scrolling a gap appears between the top part of the contentSize and the top part of the frame. This gap is known as the contentOffset
Here is the reference to UIScrollView
Here is the reference to UIScrollViewDelegate
Hope this helps!
Assuming that you have the scrollView inside self.view, you can implement scrollViewDidScroll: in the scroll view delegate to find when it is scrolled.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
for (UIView *view in self.scrollView.subviews) {
CGRect frame = [view convertRect:view.frame toView:self.view]; // Contains the frame of view with respect to self.view
}
}
You can them use the frame to resize subviews as you want.
The answer starts with analyzing the UIScrollView Class Reference and it's delegate. In the delegate documentation see the responding to scrolling and dragging section. You should also review the sample code for each. You can create outlets to your subviews and change the subview properties within a uiview animation. These references will give you a good foundation in understanding where you can build the call to animate the subviews.
Here is a link to animating subviews. Additional examples can be found by Googling "uiview subview animation" (without the quotes). If you run into any major issues read the header files first and post some sample code for additional (more precise) help.
Other reference:
UIKit ScrollViews

UIScrollView inside a movable UIView

I'm attempting to recreate the animation done in the Google Maps app that occurs when you select a location on the map. After selecting a location a UIView pokes up from the bottom of the screen. You can pull that up to reveal a UIScrollView with additional content. I'm curious how they make it so you can only scroll the content inside the UIScrollView when the parent view is at the "top". I understand that you could setScrollEnabled:, but they somehow do it on the fly so that when sliding your finger up the parent view "docks", and then the inner content scrolls, and when you are scrolling the content down, it stops once it reaches the top of the content and starts pulling the header down with it.
Any ideas?
I solved this problem by doing something like this: Create an animation that moves the scroll view's parent from the half-visible position to the top....
- (void)setScrollViewExpanded:(BOOL)expanded {
BOOL isExpanded = self.scrollViewContainer.frame.origin.y == 0.0;
if (isExpanded == expanded) return;
CGRect frame = self.scrollViewContainer.frame;
CGRect newFrame;
if (expanded) {
newFrame = CGRectMake(0, 0, frame.size.width, self.view.frame.size.height);
self.scrollView.delegate = nil;
self.scrollViewContainer.frame = CGRectMake(0, frame.origin.y, frame.size.width, self.view.bounds.size.height);
self.scrollView.delegate = self;
} else {
newFrame = CGRectMake(0, 300, frame.size.width, frame.size.height);
}
[UIView animateWithDuration:1 animations:^{
self.scrollViewContainer.frame = newFrame;
} completion:^(BOOL finished) {
if (!expanded) {
self.scrollView.delegate = nil;
self.scrollViewContainer.frame = CGRectMake(0, 300, self.scrollViewContainer.bounds.size.width, self.view.bounds.size.height-300);
self.scrollView.delegate = self;
}
}];
}
Trigger the animation based on change in scroll view's content offset relative to the top...
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y > 10.0) {
// 10 is a little threshold so we won't trigger this on a scroll view
// "bounce" at the top. alternatively, you can set scrollView.bounces = NO
[self setScrollViewExpanded:YES];
} else if (scrollView.contentOffset.y < 0.0) {
[self setScrollViewExpanded:NO];
}
}
Edit: I changed the expand animation after rechecking my old code. It needs to be a little more complex due to feedback on the content offset while changing the frame. The edit doesn't change the size during the animation, just the origin. It changes the size before or after the animation and blocks the delegate messages temporarily. Also note the scroll view's autoresize mask should be set to fill the container view.

Resources