Cursor outside UITextView - ios

I've searched high and low and can't seem to find an answer. The closest thing I've seen is here:
UITextView cursor below frame when changing frame
Sorry, no screenshots as I have (nearly) no reputation, but it looks similar to the linked SO post.
When I run the app on iOS6, things work perfectly (content scrolls with the cursor to keep it on screen), but on iOS7, the cursor goes one line beyond the end of the UITextView. I tried adding UIEdgeInsets to move the content, but when the user is actively entering text, it just keeps adding a new line until the cursor is below the end of the text view.
My layout consists of a Label (headerText) with a UITextView (textView) below it. This view is shown from a tab bar. There is a keyboard input accessory that is added, but it's height is calculated into the keyboard height automatically before the function is called.
Here is the function I use to resize my views, called from keyboard show/hide delegate, rotate, initial layout, etc:
-(void)resizeViewsWithKbdHeight:(float)kbHeight
{
//set the header label frame
//set constraints
//total view width - 30 (15 each for left and right padding)
CGFloat labelWidth = ([[[self navigationController] view] bounds].size.width - 30);
CGSize constraintSize = {labelWidth, CGFLOAT_MAX};
//calculate needed height for header label
CGSize textSize = [[self headerText] sizeWithFont:[UIFont systemFontOfSize:17.0f]
constrainedToSize:constraintSize
lineBreakMode:NSLineBreakByWordWrapping];
//build and set frame for header label:
//make it the same as the old
CGRect headerTempSize = [[self headerLabel] frame];
//except for the height
headerTempSize.size.height = textSize.height;
//and set it
[[self headerLabel] setFrame:headerTempSize];
//correct the placement of the UITextView, so it's under the header label
//build a new frame based on current textview frame
CGRect newFrame = [[self textView] frame];
//get the y position of the uitextview, the +8 is the padding between header and uitextview
CGFloat vertPadding = [[self headerLabel] frame].origin.y + [[self headerLabel] frame].size.height + 8;
//bump it down vertically
newFrame.origin.y = vertPadding;
//bump things down by the amount of the navigation bar and status bar
float offscreenBump = [[[self navigationController] navigationBar] frame].origin.y + [[[self navigationController] navigationBar] frame].size.height;
//if we aren't showing the keyboard, add the height of the tab bar
if(kbHeight == 0) {
offscreenBump += [[[self tabBarController] tabBar] frame].size.height;
}
//calculate the new height of the textview, the +9 is for padding below the text view
CGFloat newHeight = [[[self navigationController] view] bounds].size.height - ([[self textView] frame].origin.y + 9 + kbHeight + offscreenBump);
//resize the height as calculated
newFrame.size.height = newHeight;
//set textview frame to this new frame
[[self textView] setFrame:newFrame];
}
I'm trying to support for iOS5, so no AutoLayout.
It's possible I'm being incredibly naive about how I'm doing things.
Thanks in advance!

This appears to be a bug in iOS 7. The only way I've found to correct this issue is to add a delegate for the UITextView and implement textViewDidChangeSelection, resetting the view to show the selection like this:
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
- (void) textViewDidChangeSelection: (UITextView *) tView {
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
[tView scrollRangeToVisible:[tView selectedRange]];
}
}

I found a hackier yet more effective way to deal with the problem:
- (void)textViewDidChangeSelection:(UITextView *)textView
{
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
if ([textView.text characterAtIndex:textView.text.length-1] != ' ') {
textView.text = [textView.text stringByAppendingString:#" "];
}
NSRange range0 = textView.selectedRange;
NSRange range = range0;
if (range0.location == textView.text.length) {
range = NSMakeRange(range0.location - 1, range0.length);
} else if (range0.length > 0 &&
range0.location + range0.length == textView.text.length) {
range = NSMakeRange(range0.location, range0.length - 1);
}
if (!NSEqualRanges(range, range0)) {
textView.selectedRange = range;
}
}
}
Basically, I make sure that there's always a trailing space in the text field. Then, if the user tries to change the selection such that the space is revealed, change the selection. By always keeping a space ahead of the cursor, the field scrolls itself as it's supposed to.
Finally, if you need to, remove the trailing space when copying the text into your model (not shown.)

Related

Expand label that all text can fit in ( UITableViewCell )

I'm trying to create a tableviewCell with 1 label, to expand it when clicking on it and to collopase it again when again clicking on it. Now my animation on how to expand the cell is like this:
CGFloat targetHeightOfCell = [c.textLabel sizeOfMultiLineLabel].height;
_expandedState.height = #(targetHeightOfCell);
CGFloat difference = targetHeightOfCell - DEFAULT_CELL_HEIGHT;
CGFloat targetHeightOfContent = self.tableView.contentSize.height + difference;
[self.tableView beginUpdates];
[self.tableView endUpdates];
[UIView animateWithDuration:0.3f
animations:^{
CGRect frame = self.tableView.frame;
frame.size.height = targetHeightOfContent;
self.tableView.frame = frame;
}
completion:^(BOOL finished) {
}];
and in my heightForRowAtIndexPath I ofcourse return the right height. The cell expands but my calculation of sizeOfMultiLineLabel isn't correct. The text expands but still not all text is visible and so it's still appended by ...
This is my category on UILabel:
- (CGSize)sizeOfMultiLineLabel {
NSAssert(self, #"UILabel was nil");
//Label text
NSString *aLabelTextString = [self text];
//Label font
UIFont *aLabelFont = [self font];
//Width of the Label
CGFloat aLabelSizeWidth = self.frame.size.width;
//Return the calculated size of the Label
CGSize size = [aLabelTextString boundingRectWithSize:CGSizeMake(aLabelSizeWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : aLabelFont
}
context:nil].size;
//Check if the height isn't smaller then the default one
if(size.height < DEFAULT_HEIGHT){
return CGSizeMake(aLabelSizeWidth, DEFAULT_HEIGHT);
} else {
return size;
}
}
What I want is that how long the text is, the label must expand so the user can see it.
I assume that your label has numberOfLines set correctly. If not, you probably want to set it to 0 to ensure that it can have as many lines as needed. You also need to ensure the wrapping mode is correct, e.g. UILineBreakModeWordWrap. Finally, depending on your cell layout there might be margins arount the label - so ensure you account for that when returning the height.

UITextView's NSTextContainer wrong position on text view height NSLayoutConstraint change

I have subclassed UITextView to make it return an intrinsic content size like this:
- (void) layoutSubviews
{
[super layoutSubviews];
if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize])) {
[self invalidateIntrinsicContentSize];
}
}
- (CGSize)intrinsicContentSize
{
/*
Intrinsic content size of a textview is UIViewNoIntrinsicMetric
We have to build what we want here: contentSize + textContainerInset should do the trick
*/
CGSize intrinsicContentSize = self.contentSize;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f) {
intrinsicContentSize.width += (self.textContainerInset.left + self.textContainerInset.right ) / 2.0f;
intrinsicContentSize.height += (self.textContainerInset.top + self.textContainerInset.bottom) / 2.0f;
}
return intrinsicContentSize;
}
I have added an observer to the UITextViewTextDidChangeNotification and when the text view content change I update its height to make it growth with the text height:
- (void)textViewTextDidChangeNotification:(NSNotification *)notification
{
UITextView *textView = (UITextView *)notification.object;
[self.view layoutIfNeeded];
void (^animationBlock)() = ^
{
self.messageInputViewHeightConstraint.constant = MAX(0, self.messageInputView.intrinsicContentSize.height);
[self.view layoutIfNeeded];
};
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:animationBlock
completion:nil];
}
But when their is enough lines to fill the text view height, half of the time when a new line is added the NSTextContainer of the UITextView is not well placed like you can see in this picture
(The NSTextContainer is outlined in red and UITextView is outlined in blue)
The other half of the time when a new line is added the NSTextContainer is replaced correctly.
I did not found how to solve this weird behavior.
I hope one of you will have an answer to fix it.
Thank you

UITableView bigger than screen inside UIPageView

Lately I'm pulling my hair out because of this. I'm still a rookie iOS Developer therefore a simple solution would be appreciated.
I have a UIPageViewController with three views, the middle view is a UITableView which i populate with data programmatically and try to make a layout like a grid. When the UITableView has too many data the information gets outside of the screen.
I've tried to disable auto layout and add the UITableView to a UIScrollView, enabling the UITableView to scroll horizontal. I've managed to do that but somehow the interaction is messy and most of the times when trying to horizontal scroll the table it catches the UIPageViewController interaction. Also the vertical scroll of the table does not respond well too.
Is there a known solution for this situation?
After testing a few things I ended up with a acceptable solution using autolayout.
So first I added a UIScrollView and added the UITableView inside the UIScrollView.
I've enabled the Paging and disabled the Bounce Horizontally in the UIScrollView.
In the UITableView I've basically disabled everything relative to bouncing paging and scrolling.
Since the data is being populated programmatically to be some sort of a grid I have to know the total width of the longest row for that I've created a few methods to calculate the maximum width and arrange the labels.
I also created constraints for the table Width and Height which I calculate and update after all the calculations are done.
-(void) ArrangeTable
{
for (int i= 0; i < [arrayTable count]; i++) {
for (int j=0; j< [[arrayTable objectAtIndex:i ] count]; j++) {
UILabel * nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 8.0, 95.0, 30.0)];
[nameLabel setText:[NSString stringWithFormat:#"%#",[[arrayTable objectAtIndex:i] objectAtIndex:j]]];
[nameLabel sizeToFit];
float widthIs = [nameLabel.text
boundingRectWithSize:nameLabel.frame.size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{ NSFontAttributeName:nameLabel.font }
context:nil].size.width;
[self alignLabels:j currentWidth:[NSNumber numberWithFloat: widthIs]];
}
}
}
-(void) alignLabels:(int)colNumber currentWidth:(NSNumber*)labelWidth
{
if(colNumber >= [arrayLabelWidth count])
{
//Insert without position
[arrayLabelWidth addObject:labelWidth];
}else if ([labelWidth floatValue] > [[arrayLabelWidth objectAtIndex:colNumber] floatValue] )
{
[arrayLabelWidth replaceObjectAtIndex:colNumber withObject:labelWidth];
}
}
-(void) setMaxRowWidth
{
float widthTV =[[arrayLabelWidth valueForKeyPath:#"#sum.self"] floatValue] + ([arrayLabelWidth count]) * 10;
CGRect screenRect = [[UIScreen mainScreen] bounds];
float heightTV = [self tableViewHeight];
if(widthTV > screenRect.size.width)
{
tvWidth.constant = widthTV;
//svWidth.constant = valueTV;
}else{
if (UIDeviceOrientationIsPortrait(self.interfaceOrientation)){
//DO Portrait
tvWidth.constant = screenRect.size.width;
}else{
//DO Landscape
float calc = screenRect.size.width - self.view.frame.size.width;
tvWidth.constant = screenRect.size.width - calc;
}
}
tvHeight.constant = heightTV;
}
- (CGFloat)tableViewHeight
{
[tablePod layoutIfNeeded];
return [tablePod contentSize].height;
}

Incorrect size of a textview frame (self.textview.frame.size.height)

I want to change the height of a textview depending on its content. I created the method to resize its view. The first time I call this view controller it resize properly (height = 253) but not the other times (height = 296).
I tried resizing it from viewDidAppear, viewWillAppear and viewDidLoad. The first time viewDidAppear and viewDidLoad are called. Second and following times all methods (viewDidAppear, viewWillAppear and viewDidLoad) are called. I don't know the reason and I don't know why this weird behavior, any clue?
-(void) setHeight
{
NSLog(#"Set height");
CGRect frame = descriptionTextView.frame;
frame.size.height = descriptionTextView.contentSize.height;
descriptionTextView.frame = frame;
NSLog(#"height: %f", descriptionTextView.frame.size.height);
if([[UIScreen mainScreen] bounds].size.height == 568) //iPhone 4inch
{
totalHeight = 380+frame.size.height;
[self.mainScrollView setContentSize:CGSizeMake(320,totalHeight)];
}
else{
totalHeight = 250+frame.size.height;
[self.mainScrollView setContentSize:CGSizeMake(320,totalHeight)];
}
}
I use autolayout in my project but not for this view since I dont know how to properly resize a textview inside a scrollview (which also includes 2 more views with labels, images and buttons) based on the textview content with autolayout. Is it better to use autolayout than this function? Perhaps you can help me with the constraints...
You are changing only the scroll size according to your calculation but you are missing to change the size of text view itself. After setting scroll view content size update the size of the text view and all should be fine (contentSize and frame). This may look like:
[descriptionTextView setContentSize:CGSizeMake(CGRectGetWidth(descriptionTextView.frame),totalHeight)];
CGRect frame = descriptionTextView.frame;
frame.size.height = totalHeight;
[descriptionTextView setFrame:frame];
By the way: in the if-statement you can only calculate totalHeight and set it afterwards.
Additionally you do not need this line:
frame.size.height = descriptionTextView.contentSize.height;
Try this:
CGSize constraintSize;
constraintSize.height = MAXFLOAT;
constraintSize.width = yourTextView.frame.size.width;
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"yourFontName" size:yourFontSize], NSFontAttributeName,
nil];
CGRect frame = [yourTextView.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributesDictionary
context:nil];
// Use this frame for your textview

UITextView contentSize automatically decreases

I have a UITextView whose height I am trying to resize as the number of lines increases. Apart form this, I need to resize the UIView in which the UITextView is contained. I am using the following code for this:
-(void) textViewDidChange:(UITextView *)textView{
CGFloat before = textView.frame.size.height;
CGRect frame = textView.frame;
frame.size = textView.contentSize;
CGFloat after = frame.size.height, diff = after - before, final = 0;
if (diff>16) {
final = 8;
}else if(diff<-1){
final = -16;
}else{
final = 0;
}
[self.containerView setFrame: CGRectMake(self.containerView.frame.origin.x,
self.containerView.frame.origin.y-final, self.containerView.frame.size.width,
frame.size.height+13)];
[textView setFrame: frame];
}
I am also trying to change the Y-position of the container as the text is changed. I am using static number values according to font size to avoid complications.
But the problem is that whenever the UITextView enters a new line, it resizes its contentSize to the original size and so the frame of the UITextView is also reduced for a moment, which looks really ugly. Also, the Y-position depends on this contentSize change and it gets all messed up when this automatic resize happens.
I am looking for the same behaviour which apps like whatsapp or viber have. Any idea how I can achieve this?
If I understand correctly, the containerView includes a textfield and must be drawn around it, regardless of the size of the textfield? If that is, try something like this
-(void) textViewDidChange:(UITextView *)textView
{
CGRect frame = textView.frame;
frame.size = textView.contentSize;
//do your magic calculations
[textView setFrame: frame];
[self.containerView setFrame: CGRectMake(self.containerView.frame.origin.x,
self.containerView.frame.origin.y-final, self.containerView.frame.size.width, frame.size.origin.y +frame.size.height+13)];
}

Resources