I create a xib file and it includes a UITextView, I add a height constraints for UITextView. When the UIViewController load the xib view I want to change the UITextView height based on the text height. I create an IBOutlet namedheightConstraints for the height constraints and textView for UITextView. I set it in
- (void)viewWillLayoutSubview {
CGFloat height = [self getTextHeight:self.textView];
self.heightConstraints.constant = height;
[self.textView layoutIfNeeded];
}
But it does not change the height of UITextView. Can anyone help?
Can you try
- (void)viewWillLayoutSubview {
self.heightConstraints.constant = self.textView.contentSize.height;
[self.textView layoutIfNeeded];
}
Implement like this, you are required to confirm the UITextView delegate method to change it's height dynamically
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
[self updateTextViewSize:textView];
return YES
}
- (void)updateTextViewSize:(UITextView*)aTextView{
CGFloat height = [self getTextHeight:aTextView];
self.heightConstraints.constant = height;
[self.textView layoutIfNeeded];
}
- (CGFloat)getTextHeight:(UITextView*)aTextView{
/*
calculate frame based on the text and return
*/
}
I'm trying to set the ContentSize of my UIScrollView automatically, but when I do it, the frame height of uiscrollview is not with the screenHeight as I want.
There's my code:
-(void) viewWillAppear:(BOOL)animated {
[self.scrollGeral setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.scrollGeral setContentSize:(CGSizeMake(screenSize.size.width, [self calcularAlturaScrollView]))];
}
-(CGFloat) calcularAlturaScrollView {
CGFloat scrollViewHeight = 0.0f;
for (UIView* view in self.scrollGeral.subviews)
{
scrollViewHeight += view.frame.size.height;
}
return scrollViewHeight;
}
Therefore, I'have tried to include the following line:
[self.scrollGeral setFrame:CGRectMake(0, 0, screenSize.size.width, screenSize.size.height)];
But if I do it, the UIScrollView 'disabled' the scroll
I solved my problem with the following code:
-(void) viewDidLayoutSubviews {
[self.scrollGeral setContentSize:(CGSizeMake(screenSize.size.width, [self calcularAlturaScrollView]))];
}
I have a scroll view with a label in it, and I wants after someones scroll the label X px's to the right and release his finger to delete this label with animation.
So I created a delegate connection and added the scroll view delegate method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"scroll view did scroll");
}
In this method I want to say something like:
if myScrollView.someProperty moved X px's to the right and the user pulled his finger
delete this label sliding with animation to the right
Can someone please help out here :/
tnx ahead!!
Check UIScrollView's contentOffset property:
contentOffset - The point at which the origin of the content view is offset
from the origin of the scroll view.
you can use UISwipeGestureRecognizer to do this and define how many pixel you want to drag the label to the right. You can try the following code
- (void)viewDidLoad {
[super viewDidLoad];
mylabel.userInteractionEnabled=YES;
[mylabel sizeToFit];
[mylabel addGestureRecognizer:[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipeLabel:)]];
}
- (void)didSwipeLabel:(UISwipeGestureRecognizer*)swipe
{
NSLog(#"swipe");
swipe.direction = UISwipeGestureRecognizerDirectionRight;
if (swipe.direction == UISwipeGestureRecognizerDirectionRight) {
[UIView animateWithDuration:0.5 animations:^{
// swipe the label 50px right
mylabel.transform = CGAffineTransformMakeTranslation(50.0, 0.0);
} completion:^(BOOL finished) {
// when animation
[UIView animateWithDuration:0.5 animations:^{
NSLog(#"label should be removed");
[mylabel removeFromSuperview];
}];
}];
}
}
I've got a segmentedControl with three views in my app, one of which is a scrollView which works like a sort of gallery without zoom, with pageControl and an imageView at the center.
The hierarchy is like
--> Segmented Control (3 views) : descriptionView, imageTabView, shareView
----> imagesTabView (UIView)
------> scrollView
------> imageView
----> pageControl
When the device is portrait or landscape, the imageView images are shown correctly, they're centered and scrolling works perfectly fine.
The only problem is that when you turn the device again, if the image is "in the middle" (e.g. is the 2nd of 3 or the 3rd of 6), it's being shown decentered, far left or right, and with a little swipe it goes back at the center, while if the image is the first or the last one, it works properly.
I've looked here on S.O. on various threads, tried to set a contentView as a subview of the scrollView and add the imageView as subview of contentView, but didn't work, tried to attach the imageView to the bottom or the right of the scrollView but didn't work either.
I feel like I'm a step away to achieve what I want to do, the only problem is that I can't get why it's not centered.
In viewWillLayoutSubviews I've specified the contentSize, in order that when it rotates, the size it's set correctly, like
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.scrollView.contentSize = CGSizeMake (self.scrollView.frame.size.width * photosArray.count, 1);
}
Here's how I'm initializing the pageControl, the scrollView and the imageView:
-(void)configureImageTab{
pageControl = [UIPageControl new];
[pageControl addTarget:self action:#selector(changePage) forControlEvents:UIControlEventValueChanged];
pageControl.translatesAutoresizingMaskIntoConstraints = NO;
//Don't show pageControl when there are no photos
if (photosURL.count == 0)
pageControl.hidden = YES;
//Configuring scrollView
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.imageSegmentView.frame.size.width, self.imageSegmentView.frame.size.height-pageControl.frame.size.height)];
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
self.scrollView.userInteractionEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
//... Code cut - adding remote images to fetch to array
//Actual setup -> scrollView adding imageView as subview with all the images
for (int i =0; i< photosArray.count; i++){
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
//imageView setup
imageView = [[UIImageView alloc]initWithFrame:frame];
imageView.backgroundColor = [UIColor clearColor];
imageView.clipsToBounds = YES;
imageView.userInteractionEnabled = YES;
imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
imageView.translatesAutoresizingMaskIntoConstraints = YES;
imageView.contentMode = UIViewContentModeScaleAspectFit;
//Setting images urls
[imageView setImageWithURL:[NSURL URLWithString:[photosArray objectAtIndex:i]] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//Error handling
}
}usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
//Adding gesture recognizer to scrollView and imageView as subview
[self.scrollView addGestureRecognizer:singleTap];
[self.scrollView addSubview:imageView];
}
//Setting the contentSize
pageControl.numberOfPages = [photosURL count];
[self.imageSegmentView addSubview:self.scrollView];
[self.imageSegmentView addSubview:pageControl];
//Constraints
NSDictionary *views = #{#"pageControl" : pageControl, #"scrollView" : self.scrollView};
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[pageControl]-0-|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]-1-[pageControl]-1-|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics:nil views:views]];
[pageControl addConstraint:[NSLayoutConstraint constraintWithItem:pageControl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageSegmentView attribute:NSLayoutAttributeHeight multiplier:0 constant:30]];
}
#pragma mark - scrollView delegate -
-(void)scrollViewDidScroll:(UIScrollView *)sView{
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor ((self.scrollView.contentOffset.x - pageWidth /2) /pageWidth) +1;
self.pageControl.currentPage = page;
}
-(IBAction)changePage {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
[self.scrollView scrollRectToVisible:frame animated:YES];
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
pageControlBeingUsed = NO;
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
pageControlBeingUsed = NO;
}
One note to make: imageView is using autoresizingMask: without that, it wouldn't be able to show the images properly.
My guess is that probably there's something to fix within the scrollView delegate, but I'm not quite sure.
Any suggestion appreciated!
EDIT
I've noticed that the same bug occurs in Twitter app when browsing a user's pictures and then turning the device.
EDIT 2 for TL;DR
Basically, let's say I have 3 images in an horizontal scrollView with paging.
I turn the device from Portrait to Landscape on the first photo, and it's shown at its own place, correctly centered.
I move to the next photo, shown centered, and then I turn the device again to Portrait. The photo is not aligned correctly, is not centered
Practically, the first and the last images, when the device rotates multiple times, are shown centered. The others are not centered
EDIT 3
I've extracted some of the lines and made a sample project to demonstrate the issue I'm having. I guess there's definitely something up with contentSize.
We can fix the specific bug you're talking about (scroll view not aligned to page boundary after rotation) by recording the current page when the interface is about to rotate, and then setting the scroll view's contentOffset appropriately during the rotation, after the system has updated the scroll view's bounds size. Let's add a pageNumberPriorToRotation instance variable:
#implementation ViewController {
CGFloat pageNumberPriorToRotation;
}
Then, we set it when the interface is about to rotate:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self setPageNumberPriorToRotation];
}
- (void)setPageNumberPriorToRotation {
CGRect bounds = self.scrollView.bounds;
static const int kNumberOfImages = 3;
pageNumberPriorToRotation = fmin(round(bounds.origin.x / bounds.size.width),
kNumberOfImages - 1);
}
and we use it to set the scroll view's contentOffset during the interface rotation:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self updateScrollViewLayout];
}
- (void)updateScrollViewLayout {
CGRect bounds = self.scrollView.bounds;
bounds.origin.x = bounds.size.width * pageNumberPriorToRotation;
self.scrollView.bounds = bounds;
}
This takes care of your primary complaint: the scroll view will always be aligned to a page view boundary after a rotation.
However…
There are some other problems with the scroll view interaction. In landscape orientation, I can't scroll to the third image. After rotating to landscape and back to portrait, I can scroll to a blank fourth page. These problems are presumably what you meant by “there's definitely something up with contentSize”.
Furthermore, your code has a number of problems. It uses some outdated style, like explicitly declaring instance variables for properties and putting instance variables in the header file. It also suffers from Massive View Controller. It could really stand to be rewritten in modern style, and using features like UITabBarController and UIPageViewController.
Anyway, you probably have neither the time nor the inclination to do that amount of work, so I will show you how to solve the contentSize problems and slim down your VC a little at the same time.
I'll make a UIScrollView subclass called ImageScrollView. You give me the array of images and I'll take care of setting up its subviews and aligning to a page boundary after a rotation. Here's my header file:
ImageScrollView.h
#import <UIKit/UIKit.h>
#interface ImageScrollView : UIScrollView
#property (nonatomic, copy) NSArray *images;
#end
To implement this, I'll need some instance variables:
ImageScrollView.m
#import "ImageScrollView.h"
#import <tgmath.h>
#implementation ImageScrollView {
NSMutableArray *imageSubviews;
CGSize priorSize;
CGFloat pageNumber;
BOOL needsToSyncSubviewsWithImages : 1;
}
Anyway, first I'll implement the public API, which is just the images property:
#pragma mark - Public API
#synthesize images = _images;
- (void)setImages:(NSArray *)images {
_images = [images copy];
needsToSyncSubviewsWithImages = YES;
}
Note that when you set the images array, I don't immediately create the subviews. For now, I just set the needsToSyncSubviewsWithImages flag so I'll know to do it during the layout phase.
#pragma mark - UIView overrides
Next, I need to override layoutSubviews so I can do the real work during the layout phase. The system sends me layoutSubviews during the layout phase if my subviews array has changed, or if my bounds has changed.
Because I'm a scroll view, and because a scroll view's contentOffset is really just an alias for its bounds.origin, the system sends me layoutSubviews a lot: every time the scroll view scrolls. So I want to be careful to do only necessary work in layoutSubviews.
- (void)layoutSubviews {
The first thing I do is call super, which takes lets auto layout work (if you're using it) and updates my scroll indicators (if they're visible).
[super layoutSubviews];
Next, if I got new images, I set up the subviews that display them.
if (needsToSyncSubviewsWithImages) {
[self syncSubviewsWithImages];
}
Next, if I've set up new subviews, or if I've changed size, I lay out my subviews' frames for the new size, and align to a page boundary.
if (needsToSyncSubviewsWithImages || !CGSizeEqualToSize(self.bounds.size, priorSize)) {
[self layoutForNewSize];
}
Finally, I update my state.
needsToSyncSubviewsWithImages = NO;
priorSize = self.bounds.size;
[self updatePageNumber];
}
Of course, I delegated all the real work to helper methods, so now I need to implement those.
#pragma mark - Implementation details
To synchronize my subviews with my images, I need to do three things. I need to make sure I've actually allocated my imageSubviews array, I need to make sure every image is in a subview, and I need to make sure I don't have any extra image subviews (in case my images array was made smaller).
- (void)syncSubviewsWithImages {
[self ensureImageSubviewsArrayExists];
[self putImagesInSubviews];
[self removeExtraSubviews];
}
- (void)ensureImageSubviewsArrayExists {
if (imageSubviews == nil) {
imageSubviews = [NSMutableArray arrayWithCapacity:self.images.count];
}
}
- (void)putImagesInSubviews {
[self.images enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop) {
[self putImage:obj inSubviewAtIndex:i];
}];
}
- (void)removeExtraSubviews {
while (imageSubviews.count > self.images.count) {
[imageSubviews.lastObject removeFromSuperview];
[imageSubviews removeLastObject];
}
}
- (void)putImage:(UIImage *)image inSubviewAtIndex:(NSUInteger)i {
UIImageView *imageView = [self imageViewAtIndex:i];
imageView.image = image;
}
When I want to get the image view for an index, I might find that I haven't actually created enough subviews yet, so I create them on demand:
- (UIImageView *)imageViewAtIndex:(NSUInteger)i {
while (i >= imageSubviews.count) {
UIView *view = [[UIImageView alloc] init];
view.contentMode = UIViewContentModeScaleAspectFit;
view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:view];
[imageSubviews addObject:view];
}
return imageSubviews[i];
}
Note that I've set the autoresizingMask such that autoresizing won't actually modify my subview frames. Instead, I'll lay them out “manually”.
OK, now I need to implement the methods that set my subviews' frames and align to a page boundary when my size changes.
- (void)layoutForNewSize {
[self setSubviewFramesAndContentSize];
[self alignToNearestPage];
}
Setting the subview frames requires looping over them, laying them out from left to right. After I've laid out the last one, I know my contentSize. Note that I need to loop over imageSubviews only, not self.subviews, because self.subviews also contains the scroll indicators.
- (void)setSubviewFramesAndContentSize {
CGRect frame = self.bounds;
frame.origin = CGPointZero;
for (UIView *subview in imageSubviews) {
subview.frame = frame;
frame.origin.x += frame.size.width;
}
self.contentSize = CGSizeMake(frame.origin.x, frame.size.height);
}
To align to the nearest page, I set my contentOffset based on the last known page number and my new size.
- (void)alignToNearestPage {
self.contentOffset = CGPointMake(pageNumber * self.bounds.size.width, 0);
}
Finally, I need to update my page number every time I scroll, so I'll have it in case of rotation:
- (void)updatePageNumber {
// Note that self.contentOffset == self.bounds.origin.
CGRect bounds = self.bounds;
pageNumber = fmin(round(bounds.origin.x / bounds.size.width), self.images.count - 1);
}
#end
Now you can update ViewController to use the ImageScrollView. This mostly involves ripping stuff out:
-(void)configureImageTab{
//Page control
pageControl = [UIPageControl new];
pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
pageControl.pageIndicatorTintColor = [UIColor grayColor];
[pageControl addTarget:self action:#selector(changePage) forControlEvents:UIControlEventValueChanged];
pageControl.translatesAutoresizingMaskIntoConstraints = NO;
//Configuring scrollView
self.scrollView = [[ImageScrollView alloc] initWithFrame:CGRectMake(0, 0, self.imageSegmentView.frame.size.width, self.imageSegmentView.frame.size.height-pageControl.frame.size.height)];
self.scrollView.backgroundColor = [UIColor clearColor];
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
self.scrollView.userInteractionEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
//Adding imageURLS to array
photos = #[ [UIImage imageNamed:#"createBootableUSBInstallDrive1"], [UIImage imageNamed:#"createBootableUSBInstallDrive2"], [UIImage imageNamed:#"createBootableUSBInstallDrive3"]];
self.scrollView.images = photos;
pageControl.numberOfPages = [photos count];
[self.imageSegmentView addSubview:self.scrollView];
[self.imageSegmentView addSubview:pageControl];
NSDictionary *views = #{#"pageControl" : pageControl, #"scrollView" : self.scrollView};
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[pageControl]-0-|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]-1-[pageControl]-1-|" options:0 metrics:nil views:views]];
[pageControl addConstraint:[NSLayoutConstraint constraintWithItem:pageControl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageSegmentView attribute:NSLayoutAttributeHeight multiplier:0 constant:30]];
}
You also need to change the declared type of scrollView to ImageScrollView in the header file. You can eliminate the viewWillLayoutSubviews, willRotateToInterfaceOrientation:duration:, and willAnimateRotationToInterfaceOrientation:duration: methods entirely.
I've uploaded my modified version of your test project to this github repository.
I would like to create an app that has a function like iOS 7's Notes application. Basically there is one row on the top that has date and time.
My solution is to put the UITextView inside UITableView. First row is UILabel with date and time, the second row is UITextView.
I change both UITextView and UITableViewCell height according to UITextView ContentSize.
The problem is the UITextView size is large so it doesn't automatically scroll when the user hit return key.
Is there any solution to make it scroll as normal?
UITextView is a subclass of UIScrollView. I will suggest an alternative method of implementing a similar functionality. Add the label view as a subview of the text view, and set a contentInset top value of the height of the label.
UILabel* label = [UILabel new];
label.text = #"Test";
[label sizeToFit];
CGRect frame = label.frame;
frame.origin.y -= frame.size.height;
[label setFrame:frame];
[self.textView addSubview:label];
[self.textView setContentInset:UIEdgeInsetsMake(label.frame.size.height, 0, 0, 0)];
Sample project:
http://sdrv.ms/16JUlVD
Try this solution. Fix is based on inheritance. But logic can be used at any place after UITextView text was changed. I have taken some useful code blocks from here:
http://craigipedia.blogspot.ru/2013/09/last-lines-of-uitextview-may-scroll.html
and edited by me for my solution.
Should work.
#interface CustomTextView : UITextView
#end
#implementation CustomTextView
-(id)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
}
return self;
}
-(void)textDidChange:(NSNotification *)notification {
//iOS 7 UITextView auto scroll fix.
NSRange caretRange = self.selectedRange;
if (caretRange.location == self.text.length) {
CGRect textRect = [self.layoutManager usedRectForTextContainer:self.textContainer];
CGFloat sizeAdjustment = self.font.lineHeight * [UIScreen mainScreen].scale;
if (textRect.size.height >= self.frame.size.height - sizeAdjustment) {
if ([[self.text substringFromIndex:self.text.length - 1] isEqualToString:#"\n"]) {
[UIView animateWithDuration:0.2 animations:^{
[self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + sizeAdjustment)];
}];
}
}
}
//end of fix
}
#end