I am trying to add a Custom View to an existing UIScrollView programmatically. I surfed the net and found possible solutions but the solution mess up the existing Autolayout. I need to add a vertical space between the super view of the custom view(i.e.- scroll view) and the custom view. So that whenever a user switches from a larger screen(iPhone5) to smaller(say iPhone4s) or vice versa the custom view is adjusted accordingly on the screen.
-(void) createEventsOnScroll
{
[imageScroller setContentSize:CGSizeMake(self.imageArray.count*imageScroller.frame.size.width, imageScroller.frame.size.height)];
[imageScroller setContentOffset:CGPointMake(imageScroller.frame.size.width*self.selectedEventIndex,0)];
imageScroller.showsHorizontalScrollIndicator = NO;
for (int i = 0; i<self.imageArray.count; i++)
{
EventCustomView *event = [[EventCustomView alloc]initWithFrame:CGRectMake(i*320+5, 3, 310, 435)];
ChildImage *child = [self.imageArray objectAtIndex:i];
NSData *data = [NSData dataWithContentsOfFile:child.imageURL];
event.eventImage.image = [UIImage imageWithData:data];
[event.eventImage setContentMode:UIViewContentModeScaleAspectFit];
event.noteView.delegate = self;
[imageScroller addSubview:event];
event.translatesAutoresizingMaskIntoConstraints = NO;
imageScroller.translatesAutoresizingMaskIntoConstraints = NO;
NSMutableArray *constraints = [[NSMutableArray alloc]init];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"|[event]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(event)]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[event]-5-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(event)]];
}
imageScroller.bounces = NO;
imageScroller.delegate = self;
[imageScroller setClipsToBounds:NO];
imageScroller.scrollEnabled = YES;
imageScroller.pagingEnabled = YES;
}
Thanks in Advance.
Related
I have the following UIView hierarchy:
-UIView
-UIScrollView
My constraint for UIScrollview with relation to it's super view are very simple:
#"H:|-%f-[%#]-%f-|"
and
#"V:|-%f-[%#]-%f-|"
They are working as expected.
I am trying to add a UIImageView as subview of scrollview Horizontal.
So my view hierarchy will become:
-UIView
-UIScrollView
-UIImageView
I am adding UIImageView as subview programmatically in UIScrollView using a for loop.
In the for loop, how can I achieve:
[SuperView]-10-[scrollview]-10-[UIImageView]-10-[UIImageView]-10-[UIScrollView]-10-[SuperView]
The problematic section is the bold part.
What I have tried:
for(int i=1;i<3;i++)
{
UIImageView *image = [[UIImageView alloc] init];
[image setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%d.jpg",i]]];
image.translatesAutoresizingMaskIntoConstraints = NO;
[_scrollView addSubview:image];
UIView *superView = _scrollView;
NSDictionary * views = NSDictionaryOfVariableBindings(superView, image);
NSString *formate = [NSString stringWithFormat:#"H:|-%f-[%#]-%f-|", scrollViewLeftMarginFromParent, #"image", scrollViewRightMarginFromParent];
NSArray * WIDTH_CONSTRAINT = [NSLayoutConstraint constraintsWithVisualFormat:formate options:0 metrics:nil views:views];
formate = [NSString stringWithFormat:#"V:|-%f-[%#]-%f-|", scrollViewTopMarginFromParent, #"image", scrollViewBottomMarginFromParent];
NSArray * HEIGHT_CONSTRAINT = [NSLayoutConstraint constraintsWithVisualFormat:formate options:0 metrics:nil views:views];
[superView addConstraints:WIDTH_CONSTRAINT];
[superView addConstraints:HEIGHT_CONSTRAINT];
}
The approach I can think of:
LeftSide:
[scrollview]-10-[UIImageView]
Right side:
[UIImageView]-10-[scrollview]
in between:
[UIImageView]-10-[UIImageView]
If it's the right approach, then how do I achieve this in for loop.
If it's not then what is best approach.
It's quite simple actually. Your approach is correct, all you need is how you convert that into code. I will try to simplify this for you. I am assuming a UIImageView's width & height as 100. You can change as you like
-(void)setUI
{
lastView = nil; //Declare a UIImageView* as instance var.
arrayCount = [array count]; //In your case a static count of 3
for(NSInteger index =0; index < arrayCount; index++)
{
UIImageView *view = [[UIImageView alloc] init];
[self.mainScroll addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[view(100)]-20-|" options:0 metrics:nil views:#{#"view":view}]];
//--> If view is first then pin the leading edge to main ScrollView otherwise to the last View.
if(lastView == nil && index == 0) {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[view(100)]" options:0 metrics:nil views:#{#"view":view}]];
}
else {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[lastView]-10-[view(100)]" options:0 metrics:nil views:#{#"lastView":lastView, #"view":view}]];
}
//--> If View is last then pin the trailing edge to mainScrollView trailing edge.
if(index == arrayCount-1) {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[view]-10-|" options:0 metrics:nil views:#{#"view":view}]];
}
//--> Assign the current View as last view to keep the reference for next View.
lastView = view;
}
}
I had encountered similar situation where my scrollview along with its content view was created from IB, but the subviews were added programatically. Writing constraints for subviews was making the View controller bloated. Also the for loop was was getting a lots of ifs and elses,hence I wrote a UIView Subclass to handle this scenario.
Change the class type for you Content View in IB, get a reference of it, add subviews through directly setting the property stackViewItems,or methods -(void)insertStackItem:, -(void)insertStackItem:atIndex:
#import "IEScrollContentView.h"
#interface IEScrollContentView()
{
NSMutableArray * _stackViewItems;
}
#property (nonatomic,strong) NSLayoutConstraint * topConstraint;
#property (nonatomic,strong) NSLayoutConstraint * bottomConstraint;
#end
#implementation IEScrollContentView
#synthesize stackViewItems = _stackViewItems;
//-----------------------------------------------------------------//
#pragma mark - Init Methods
//-----------------------------------------------------------------//
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder])
_stackViewItems = [NSMutableArray new];
return self;
}
-(instancetype)initWithFrame:(CGRect)frame {
if(self = [super initWithFrame:frame])
_stackViewItems = [NSMutableArray new];
return self;
}
//-----------------------------------------------------------------//
#pragma mark - Public Methods
//-----------------------------------------------------------------//
-(void)setStackViewItems:(NSArray *)stackViewItems {
if(!_stackViewItems)
_stackViewItems = [NSMutableArray new];
for (UIView * view in stackViewItems) {
[self insertStackItem:view];
}
}
-(void)insertStackItem:(UIView *)stackItem
{
[self insertStackItem:stackItem atIndex:_stackViewItems.count];
}
-(void)insertStackItem:(UIView *)stackItem atIndex:(NSUInteger)index
{
if(!stackItem || index > _stackViewItems.count)return;
if(index == 0)
[self addView:stackItem
belowView:self
aboveView:_stackViewItems.count>0?_stackViewItems.firstObject:self];
else if(index==_stackViewItems.count)
[self addView:stackItem
belowView:_stackViewItems[index-1]
aboveView:self];
else
[self addView:stackItem
belowView:_stackViewItems[index-1]
aboveView:_stackViewItems[index]];
}
//-----------------------------------------------------------------//
#pragma mark - Constraining Views
//-----------------------------------------------------------------//
-(void)addView:(UIView *)view belowView:(UIView *)viewAbove aboveView:(UIView *)viewBelow {
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
NSArray * defaultConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)];
NSLayoutConstraint * upperConstraint,* lowerConstraint;
if(viewAbove==self) {
[self removeConstraint:_topConstraint];
upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
_topConstraint = upperConstraint;
}
else
upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewAbove]-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewAbove)].firstObject;
if(viewBelow==self) {
[self removeConstraint:_bottomConstraint];
lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
_bottomConstraint = lowerConstraint;
}
else
lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view]-0-[viewBelow]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewBelow)].firstObject;
[self addConstraints:defaultConstraints];
[self addConstraints:#[upperConstraint,lowerConstraint]];
[_stackViewItems addObject:view];
}
#end
I have uploaded the files here
IEScrollContentView.h
IEScrollContentView.h.m
I have a customUIView subclass here(which is self here), and it uses Autolayout and Size class(wAny hAny) in the storyboard.
Also, there is aUIScrollView in it and 6 UIImageViews in theUIScrollView as its paging content.
In order to get the correct width of self(320 pt but not the default width 600 or 568 in storyboard), I have to set the frame ofUIImageView in layoutSubviews, but it causes infinite loop here.
On the other hand, if I add imageViews inawakeFromNib, it gets the incorrect width of self which is 584(600-16).
Where should I add subviews (6 imageViews) to the scrollView to avoid infinite loop ?
#define Self_Width CGRectGetWidth(self.bounds)
#define Self_Height CGRectGetHeight(self.bounds)
#interface YSAdScrollView ()<UIScrollViewDelegate>
#property(nonatomic) NSArray *coverAdImages;
#property(nonatomic) UIScrollView *ysCoverAdScrollView;
#end
#implementation YSAdScrollView
-(void)layoutSubviews{
[super layoutSubviews];
[self addSubview:self.ysCoverAdScrollView];
self.ysCoverAdScrollView.contentSize = CGSizeMake(Self_Width * self.coverAdImages.count , Self_Height);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[ysCoverAdScrollView]-0-|" options:0 metrics:nil views:self.viewsDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[ysCoverAdScrollView(106)]->=0-|" options:0 metrics:nil views:self.viewsDictionary]];
[self.coverAdImages enumerateObjectsUsingBlock:^(NSString *imageName, NSUInteger idx, BOOL *stop) {
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, idx * Self_Width, Self_Height)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.translatesAutoresizingMaskIntoConstraints = YES;
imageView.image = [UIImage imageNamed:imageName];
imageView.layer.borderWidth =1 ;
NSLog(#"imageView[%d] = %#" , (int)idx , NSStringFromCGRect(imageView.frame));
[self.ysCoverAdScrollView addSubview:imageView];
}];
}
-(UIScrollView *)ysCoverAdScrollView{
if (!_ysCoverAdScrollView) {
self.ysCoverAdScrollView = [[UIScrollView alloc]
initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
self.ysCoverAdScrollView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _ysCoverAdScrollView;
}
-(NSArray *)coverAdImages{
if (!_coverAdImages) {
_coverAdImages = [NSArray arrayWithObjects:#"coverAd4.jpg",#"coverAd1.jpg",#"coverAd2.jpg",#"coverAd3.jpg",#"coverAd4.jpg",#"coverAd1.jpg", nil];
}
return _coverAdImages;
}
I have 2 view controllers embedded in a view vertically. View controller A (uploader), and B (docList).
B contains a UICollectionView
All, methods except cellForItemAtIndexPath inside the datasource of the collection view get called correctly, and i double checked everything. There is 1 section. There are more than 0 rows. The size of the rows I return is smaller than the collection view, etc
Here's a diagram to illustrate the setup:
My issue is:
Unless i turn on setTranslatesAutoresizingMaskIntoConstraints to YES, cellForItemAtIndexPath will never be called. If i set that property to YES on the View Controller B's view, then it does get called. But the layout is then screwed up, because I am not using springs and struts. We only use constraints here.
Do you know what i can be doing wrong when embedding the view controller that contains the UICollectionView?
Here is the code that embeds the two view controllers' views, and sets them as child controllers:
- (MFFormBaseCell *)cellForComponent
{
self.cell = [[MFFormBaseCell alloc] initWithFrame:CGRectZero];
[self.cell addSubview: uploader.view];
[self.cell addSubview: docList.view];
UIView* uploaderView = uploader.view;
UIView* docListView = docList.view;
NSMutableArray* tempConstraints = [[NSMutableArray alloc]init];
[tempConstraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"V:|-8-[uploaderView]-1-[docListView]-8-|"
options: NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views: NSDictionaryOfVariableBindings(uploaderView, docListView)]];
[tempConstraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"H:|-[uploaderView]-|"
options: NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views: NSDictionaryOfVariableBindings(uploaderView)]];
[tempConstraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"H:|-[docListView]|"
options: NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views: NSDictionaryOfVariableBindings(docListView)]];
uploaderConstraints = [tempConstraints copy];
[self.cell addConstraints: uploaderConstraints];
[self.embedder addChildViewController:uploader];
[uploader didMoveToParentViewController:self.embedder];
[self.embedder addChildViewController:docList];
[docList didMoveToParentViewController:self.embedder];
docList.view.frame = self.cell.bounds;
return self.cell;
}
And here is the code from View Controller B, that sets up the UICollectionView and a vertical flow layout for it.
- (void)modelDidLoad
{
_dataSource = [[MFCardDataSource alloc] initWithData: self.cardModel];;
UICollectionViewFlowLayout *aFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[aFlowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
_collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:aFlowLayout];
[_collectionView setDelegate: self];
[_collectionView setDataSource:_dataSource];
[_collectionView setBackgroundColor:[UIColor clearColor]];
for (NSString* type in [MFCardCollectionModel typeArray])
[_collectionView registerClass:[MFImageCard class] forCellWithReuseIdentifier: type];
[_collectionView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:_collectionView];
[self registerConstraintsForView:_collectionView];
if ([self respondsToSelector:#selector(edgesForExtendedLayout)])
self.edgesForExtendedLayout = UIRectEdgeNone;
[super modelDidLoad];
}
And the contents of registerConstraintsForView:
-(void) registerConstraintsForView:(UIView*)collectionView
{
NSDictionary* metrics = #{ #"padding": #PADDING };
NSDictionary* views = NSDictionaryOfVariableBindings(_collectionView);
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-padding-[_collectionView]-padding-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:metrics
views:views]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-padding-[_collectionView]-padding-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:metrics
views:views]];
}
I got around the problem by Subclassing UICollectionView, and using the subclass instead.
On the subclass I overrode 2 methods:
- (void) setContentSize:(CGSize)contentSize
{
CGSize origSize = self.contentSize;
[super setContentSize:contentSize];
if (!CGSizeEqualToSize(contentSize, origSize))
{
[self invalidateIntrinsicContentSize];
}
}
- (CGSize) intrinsicContentSize
{
return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}
Then, i have a method inside my view controller which contains the Collection View, which calculates the required Height needed for the collection view.
I then call setContentSize with the calculated height on my subclass of Collection View, and that makes sure that it returns an intrinsicContentSize as tall as it needs to be to show all the card records inside it.
I have a UICollectionView inside of a UITableViewCell, and the CollectionView cells show images that a user stores in the app. When the user taps the cell, the image is presented full screen. So I created a UIViewController, added a UIScrollView to it for zooming, then added a UIImageView to the ScrollView. Everything works nicely until the user changes the orientation of the phone. When they change to landscape the view adjusts but the scrollview and image view do not adjust, and therefore just show on the left side of the screen; they seem to keep the same bounds. I tried to fix this by following apple's tech note using the pure layout approach and adding constraints. But when I do this, the image that is displayed is zoomed all the way in and I cannot get it to fit correctly. When I use an NSLog statement to determine the frames, they are all set to self.view.frame and therefore printout the size of the screen, but the image still shows at full size. Everything is created programmatically, no xib files.
I would love to avoid the constraints, but they seem necessary for the user to be able to see images correctly in landscape mode. I have tried setting the contentSize of the scrollview, setting clipsToBounds to YES and changing the contentMode but none of these things have any effect. Any help is greatly appreciated!
collectionView:didSelectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary *viewsDictionary;
//creating ViewController to be presented
UIViewController *fullImageView = [[UIViewController alloc] init];
fullImageView.view.userInteractionEnabled = YES;
fullImageView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[[UIApplication sharedApplication] setStatusBarHidden:YES];
_selectedImageView = [[UIImageView alloc] initWithFrame:self.view.frame];
NSString *key = [self.viewItem.imageKeyArray objectAtIndex:indexPath.row];
_selectedImageView.image = [self.viewItem.itemImages objectForKey:key];
_selectedImageView.clipsToBounds = YES;
_selectedImageView.contentMode = UIViewContentModeScaleAspectFit;
_selectedImageView.translatesAutoresizingMaskIntoConstraints = NO;
UIScrollView *imageScrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
imageScrollView.delegate = self;
imageScrollView.minimumZoomScale = 1;
imageScrollView.maximumZoomScale = 2;
imageScrollView.translatesAutoresizingMaskIntoConstraints = NO;
[fullImageView.view addSubview:imageScrollView];
[imageScrollView addSubview:_selectedImageView];
viewsDictionary = NSDictionaryOfVariableBindings(imageScrollView, _selectedImageView);
[fullImageView.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageScrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[fullImageView.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageScrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[imageScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_selectedImageView]|" options:0 metrics: 0 views:viewsDictionary]];
[imageScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_selectedImageView]|" options:0 metrics: 0 views:viewsDictionary]];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissFullImageView)];
[fullImageView.view addGestureRecognizer:tap];
[self presentViewController:fullImageView animated:YES completion:nil];
}
I have a problem with a custom view and autolayout. To make things simple I will use two UILabels, the first one should change its background color when the device rotate. The problem is that it doesn't do it! Any hint?
Thanks!
Nicola
- (id)init
{
self = [super init];
if (self) {
//Add the subviews to the mainView
[self.view addSubview:self.label1];
[self.view addSubview:self.label2];
//Autolayout
//Create the views dictionary
NSDictionary *viewsDictionary = #{#"header":self.label1,
#"table": self.label2};
//Create the constraints using the visual language format
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat: #"H:|[header]|"
options:0
metrics:nil
views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat: #"H:|[table]|"
options:0
metrics:nil
views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[header(==50)][table]|"
options:0
metrics:nil
views:viewsDictionary]];
}
return self;
}
-(UIView*) label1
{
_label1 = [UILabel alloc] init];
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
_label1.backgroundColor = [UIColor redColor];
}else{
_label1.backgroundColor = [UIColor greenColor];
}
_label1.translatesAutoresizingMaskIntoConstraints=NO;
return _label1;
}
-(UIView*) label2
{
_label2 = [UILabel alloc] init];
_label2.backgroundColor = [UIColor yellowColor];
_label2.translatesAutoresizingMaskIntoConstraints=NO;
_return label2;
}
-(BOOL) shouldAutorotate
{
return YES;
}
-(NSUInteger) supportedInterfaceOrientations
{
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
//I am on a pad
return UIInterfaceOrientationMaskAll;
} else {
//I am on a Phone
return UIInterfaceOrientationMaskAllButUpsideDown;
}
}
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//I expect the label1 to change its background color
[self.view setNeedDisplay];
}
If you move the code related to [self.label1 setBackgroundColor:] to the delegate method didRotateFromInterfaceOrientation:, it should work better. Also, in your custom getters, you are allocating a new label every time you access the method. In most situations it's preferable to check if the ivar is not nil at the beginning, and returning the ivar, instead for allocating a fresh label.