iOS autolayout with resize - ios

I have two views one is textView and below it is a scrollview, in my application the textView should toggle expand when touch it.I set the "Top Space to: Text View" for scrollView in IB.But When I expand the textView it seems not work.
here is the toggle expand code.
- (void)onTextViewClicked:(id)sender
{
CGRect targetFrame = _descTextView.frame;
if (_isTextViewExpand) {
targetFrame.size.height = _descTextViewNormalHeight;
_descTextView.frame = targetFrame;
_isTextViewExpand = NO;
[_contentScrollView setContentSize:CGSizeMake(320.f, kContentScrollViewDefaultHeight)];
}
else {
targetFrame.size.height = _descTextViewExpandHeight;
_descTextView.frame = targetFrame;
_isTextViewExpand = YES;
[_contentScrollView setContentSize:CGSizeMake(320.f, kContentScrollViewDefaultHeight + ( _descTextViewExpandHeight - _descTextViewNormalHeight ))];
}
}

You need a different approach entirely. With Auto Layout, you should never call -setFrame:. If you want the views to grow you should add a constraint or edit one of the existing constraints and then call either -setNeedsUpdateConstraints or -layoutIfNeeded.
Or just turn off Auto Layout.

Related

UIView subview autolayout issue

I have UITableView Cell in which 4 UIViews are arranged in a vertical order like this,
In each UIView, I have two Labels like this,
I have taken a IBOutlet for Height Constraint of all of the Four UIViews and I want to make the height 0 of the view when data is not available in that view. The problem is View is not getting 0 height due to the 10 px bottom constraint of UILabel inside that View.
What i am getting is like this,
Code where constraints are handled
NSString *toName = [self jointNameFromArray:evidence.arrTo];
if ([Utility isEmptyString:toName]) {
cell.toViewHeight.constant = 0;
}
else {
cell.lblToName.text = toName;
}
NSString *fromName = [self jointNameFromArray:evidence.arrFrom];
if ([Utility isEmptyString:fromName]) {
cell.fromViewHeight.constant = 0;
}
else {
cell.lblFromName.text = fromName;
}
NSString *ccName = [self jointNameFromArray:evidence.arrCc];
if ([Utility isEmptyString:ccName]) {
cell.ccViewHeight.constant = 0;
}
else {
cell.lblCCName.text = ccName;
}
NSString *custName = [self jointNameFromArray:evidence.arrCustodian];
if ([Utility isEmptyString:custName]) {
cell.custViewHeight.constant = 0;
}
else {
cell.lblCustName.text = custName;
}
[cell layoutIfNeeded];
If you're targeting iOS 9+, I'd suggesting using UIStackView, as it does exactly what you want just by setting the .hidden property of your view to true.
https://www.raizlabs.com/dev/2016/04/uistackview/
If you are unable to use UIStackView, you will need to set one of the vertical padding constraints to a lower priority than the height constraint. You will also need to set .clipsToBounds to true.
Set constraint programatically and write logic as u want it is good idea and dont forgot to set layoutifnedded.

iOS7 UITextView scrollEnabled=YES height

I am doing a test project, and came across a problem with UITextView.
I am dynamically getting the content size of the text in the text view, and then increasing its height when needed. When the height reaches the threshold I have set, I will set scrollEnabled = YES to enable scrolling. Weird thing seems to happen as shown in the following screen shots:
Before going to new line and enabling scrolling:
After entering the next character, which will enable the scrolling:
After that, entering another character again, the text view will become normal again with scroll enabled (in fact the height remains as in the previous screen shot, I change the height according to content size, so it become the same height before enable scroll):
Anyone has came across this problem and able to solve it? If this is an iOS7 bug, any other suggestion for creating a message input text box? I wonder if previous iOS versions have this problem though.
Edited:
It seems like this problem occurs when the textview's scrollEnabled is YES and change the textview.frame.size.height, then the height will reset to the initial height (as in the height set in Interface Builder). Wonder if this will help for this problem.
The following shows the code used for editing the height of the text view (it is a method for the selector which will be called upon received UITextViewTextDidChangeNotification):
NSInteger maxInputFieldWidth = self.inputTextField.frame.size.width;
CGSize maxSize = CGSizeMake(maxInputFieldWidth, 9999);
CGSize neededSize = [self.inputTextField sizeThatFits:maxSize];
NSInteger neededHeight = neededSize.height;
if (self.inputTextField.hasText)
{
[self.inputTextField scrollRangeToVisible:NSMakeRange([self.inputTextField.text length], 0)];
if (neededHeight <= TEXTVIEW_MAX_HEIGHT_IN_USE && neededHeight != previousHeight)
{
previousHeight = neededHeight;
CGRect inputTextFieldFrame = self.inputTextField.frame;
inputTextFieldFrame.size.height = neededHeight;
inputTextFieldFrame.origin.y = TEXTVIEW_ORIGIN_Y;
self.inputTextField.frame = inputTextFieldFrame;
}
else if (neededSize.height > TEXTVIEW_MAX_HEIGHT_IN_USE)
{
if (!self.inputTextField.scrollEnabled)
{
self.inputTextField.scrollEnabled = YES;
CGRect inputTextFieldFrame = self.inputTextField.frame;
inputTextFieldFrame.size.height = TEXTVIEW_MAX_HEIGHT_IN_USE;
inputTextFieldFrame.origin.y = TEXTVIEW_ORIGIN_Y;
self.inputTextField.frame = inputTextFieldFrame;
}
else if (neededHeight != previousHeight)
{
previousHeight = neededHeight;
CGRect inputTextFieldFrame = self.inputTextField.frame;
inputTextFieldFrame.size.height = TEXTVIEW_MAX_HEIGHT_IN_USE;
inputTextFieldFrame.origin.y = TEXTVIEW_ORIGIN_Y;
self.inputTextField.frame = inputTextFieldFrame;
}
}
}
Over a year later and scrollEnabled is still causing problems. I had a similar issue where setting scrollEnabled = true (I'm using Swift) would not cause any changes.
I solved the problem by setting autolayout constraints on all sides of the textView. Then, like you detailed here, I just set textView.frame again. My guess is that this causes some internal update, which actually turns scrolling on. I'm also guessing that autolayout then forces the textView to stay at the right height, as opposed to the collapse that you're experiencing.
The brilliant Pete Steinberger has had a lot of problems with the UITextView and implemented a lot of fixes as a result.
His article can be found here with links to his code.
For a direct link to the code, it can be found here, but I recommend reading the post.
I ran into a similar issue (I'm using auto-layout) and was able to solve it with the following set up:
Adding top, leading, bottom, trailing margin constraints to my text view
Adding a greater-than-or-equal-to minimum height constraint with priority 999 (in my case this was set to 50)
Adding a less-than-or-equal-to maximum height constraint with priority 1000 (in my case this was set to 125)
Adding an equal-to height constraint with priority 1000 (set to 125) and making sure it's not installed (uncheck the 'installed' option in Interface Builder or set 'active' to NO/false on the constraint in code)
I then use the following code to determine the height of the text view and enable/disable scroll and constraints:
- (void)textViewDidChange:(UITextView *)textView {
...
CGSize size = textView.bounds.size;
CGSize newSize = [textView sizeThatFits:CGSizeMake(size.width, CGFLOAT_MAX)];
if (newSize.height >= self.textViewMaxHeightConstraint.constant
&& !textView.scrollEnabled) {
textView.scrollEnabled = YES;
self.textViewHeightConstraint.active = YES;
} else if (newSize.height < self.textViewMaxHeightConstraint.constant
&& textView.scrollEnabled) {
textView.scrollEnabled = NO;
self.textViewHeightConstraint.active = NO;
}
...
}
Using sizeThatFits: to determine the desired size of the text view, I either set scroll enabled or disabled. If it's enabled, I set the height constraint to active to force the text view to stay at the desired height.

Autolayout Constraints on empty UIView do not work as expected

So I have setup a UIView that contains a UIScrollView (and child content view) that has sub views that are series of UILabels and UIViews that grow and shrink depending on the content contained in them, all using AutoLayout from the Storyboard. This works when I have something like Label - Label - Label - View w/o any issues, however if I put an empty UIView in-between two labels and insert sub views on the UIView, I'm not seeing the results I'm expecting. I have the following layout in a storyboard:
...where the teal and blue views are labels that grow to infinite height and the orange view (optionsPanel) is an empty UIVIew that I later inject sub views into. The rest of the stuff on the window is UILabels and UISegment controls. Between each row of views I have a Vertical Space constraint with a constant of 8. This all worked beautifully until I had to put in the empty UIView and programmatically inject sub views. The code I would expect to work would be something like (optionsPanel is the orange colored UIView)...
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
...where the orange UIView would magically grow and everything would just get pushed around accordingly, however this is what I'm seeing:
...the orange UIView does not grow at all, and the other two top UIView have gone somewhere off the screen. So my next guess was to turn off the Autoresizing Mask using...
optionsPanel.translatesAutoresizingMaskIntoConstraints = NO;
...but I'm getting a result where everything appears to be working but the orange UIView (optionsPanel) has no height for whatever reason and looks like:
This is getting closer to what I would expect, so I thought I would force the height of the orange UIView using code like...
frame = optionsPanel.frame;
frame.size.height = lastTop;
optionsPanel.frame = frame;
...but this appears to have no affect on anything.
Purely guessing, I found that this code almost works, if I arbitrary set the optionPanel's origin to something much larger than the space that is needed....
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
lastTop += 10;
frame = optionsPanel.frame;
frame.size.height = lastTop;
frame.origin.y += 300; //some arbitrarily‎ large number
optionsPanel.frame = frame;
..which gives this result:
...but apparently the AutoLayout has decided that the name label needs to take up the extra space. Its an ugly approach but if I could figure out how much space I need then I could just push everything down, if I had to. What's the secret to having a dynamic UIView between two dynamically sized labels and everything just work???
As #Timothy says, you need to manually add constraints to the subviews of the orange view if you want it to resize based on its contents—views don’t do this by default.
In general, if you’re using autolayout in a window, you should never be manually setting the frame of any view. Autolayout overrides any frames you set the every time it’s called, so even if you manage to manually get it working for a second it’ll fail the next time anything triggers a layout.
For views created in code, it's perfectly fine to set their frames as long as their translatesAutoresizingMaskIntoConstraints property is YES (the default, by the way).
However, for a view instantiated in storyboard or a nib, you can not set its translatesAutoresizingMaskIntoConstraints to YES.

what to use instead of scrollRangeToVisible in iOS7 or TextKit

In previous versions of iOS, my UITextView will scroll to the bottom using
[displayText scrollRangeToVisible:NSMakeRange(0,[displayText.text length])];
or
CGFloat topCorrect = displayText.contentSize.height -[displayText bounds].size.height;
topCorrect = (topCorrect<0.0?0.0:topCorrect);
displayText.contentOffset = (CGPoint){.x=0, .y=topCorrect};
But the former will now have the weird effect of starting at the top of a long length of text and animating the scroll to the bottom each time I append text to the view. Is there a way to pop down to the bottom of the text when I add text?
textView.scrollEnabled = NO;
[textView scrollRangeToVisible:NSMakeRange(textView.text.length - 1,0)];
textView.scrollEnabled = YES;
This really works for me in iOS 7.1.2.
For future travelers, building off of #mikeho's post, I found something that worked wonders for me, but is a bit simpler.
1) Be sure your UITextView's contentInsets are properly set & your textView is already firstResponder() before doing this.
2) After my the insets are ready to go, and the cursor is active, I call the following function:
private func scrollToCursorPosition() {
let caret = textView.caretRectForPosition(textView.selectedTextRange!.start)
let keyboardTopBorder = textView.bounds.size.height - keyboardHeight!
// Remember, the y-scale starts in the upper-left hand corner at "0", then gets
// larger as you go down the screen from top-to-bottom. Therefore, the caret.origin.y
// being larger than keyboardTopBorder indicates that the caret sits below the
// keyboardTopBorder, and the textView needs to scroll to the position.
if caret.origin.y > keyboardTopBorder {
textView.scrollRectToVisible(caret, animated: true)
}
}
I believe this is a bug in iOS 7. Toggling scrollEnabled on the UITextView seems to fix it:
[displayText scrollRangeToVisible:NSMakeRange(0,[displayText.text length])];
displayText.scrollEnabled = NO;
displayText.scrollEnabled = YES;
I think your parameters are reversed in NSMakeRange. Location is the first one, then how many you want to select (length).
NSMakeRange(0,[displayText.text length])
...would create a selection starting with the 0th (first?) character and going the entire length of the string. To scroll to the bottom you probably just want to select a single character at the end.
This is working for me in iOS SDK 7.1 with Xcdoe 5.1.1.
[textView scrollRangeToVisible:NSMakeRange(textView.text.length - 1,0)];
textView.scrollEnabled = NO;
textView.scrollEnabled = YES;
I do this as I add text programmatically, and the text views stays at the bottom like Terminal or command line output.
The best way is to set the bounds for the UITextView. It does not trigger scrolling and has an immediate effect of repositioning what is visible. You can do this by finding the location of the caret and then repositioning:
- (void)userInsertingNewText {
UITextView *textView;
// find out where the caret is located
CGRect caret = [textView caretRectForPosition:textView.selectedTextRange.start];
// there are insets that offset the text, so make sure we use that to determine the actual text height
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;
// only set the offset if the caret is out of view
if (textViewHeight < caret.origin.y) {
[self repositionScrollView:textView newOffset:CGPointMake(0, caret.origin.y - textViewHeight)];
}
}
/**
This method allows for changing of the content offset for a UIScrollView without triggering the scrollViewDidScroll: delegate method.
*/
- (void)repositionScrollView:(UIScrollView *)scrollView newOffset:(CGPoint)offset {
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = offset;
scrollView.bounds = scrollBounds;
}

Split the full contents of cell between UILabel and UIButton

I've got a subclassed UITableViewCell. I'm dynamically adding a UILabel and UIButton to it.
Right now I've overridden layoutSubviews and am setting the x,y absolutely of the button and label. To be able to accommodate both screen layouts as well as larger screens I'd like to make this automatic. Is there a way to tell the label to "float left" and the button to "float right?" Ideally the label should use up all space that the button doesn't need (the button is going to be a fixed size for the most part).
this property of uiview should get you started
You can try something like this assuming cellLbl is the UILabel and cellBtn is the UIButton:
- (void) layoutSubviews
{
CGRect rctFrm;
CGFloat flW;
CGFloat flH;
int iSpacing = 4; // This could be fixed for a percentage of cell width
[super layoutSubviews];
flW = self.contentView.bounds.size.width;
flH = self.contentView.bounds.size.height;
rctFrm = self.cellBtn.frame;
flW -= rctFrm.size.width + iSpacing;
rctFrm.origin.x = flW; // Right justify button
rctFrm.origin.y = (flH - rctFrm.size.height) / 2; // Center button vertically
[self.cellBtn setFrame:rctFrm];
rctFrm = self.cellLbl.frame;
rctFrm.origin.x = iSpacing;
rctFrm.size.width = flW - (2 * iSpacing);
// You can adjust UILabel vertical position and height if desired
[self.cellLbl setFrame:rctFrm];
}

Resources