Multiline UILabel inside UIScrollView - ios

I can't make multiple lines for a label inside the scroll view. Though it perfectly works outside of a scrollview. See picture below for details. The scroll view has a horizontal scroll.
UPD: ScrollView and label constraints:

So apparently I need explicitly set ScrollView contentSize. Also to get this size I need to know it from a label which is a bit tricky here. The full code goes below:
// create a label as usual
let label = UILabel()
label.numberOfLines = 0
label.text = "really long label text..."
// place it with a nice offset and set its width explicitly using parent view width
// after which `sizeToFit` does a trick and adjusts it's height
label.frame.offset(dx: 16.0, dy: 16.0)
label.frame.size.width = self.view.frame.width - 32
label.sizeToFit()
// now add it to the scroll view and set content size to label size plus margin at the bottom
self.scrollView.addSubview(label)
self.scrollView.contentSize = label.frame.size
self.scrollView.contentSize.height += 32

Related

How to properly resize textview with exclusionPaths inside of table header view

I have added a view to my tableView header consisting of an imageView and a textView. The image view is left aligned in the top corner and the textview extends over the imageview to the right side of the screen like as follows.
The textView can have dynamic content and has an exclusion path set as follows:
let imagePath = UIBezierPath(rect: imageView.frame)
self.textView.textContainer.exclusionPaths = [imagePath]
I have disabled scrolling for the textview and have set the following constraints inside of the header view:
TextView: left - 8px, right - 8px, top - 0px, bottom - 8px
ImageView: left - 8px, width - 100px, height 100px, top - 8px, bottom - greater than or equal to 8px
I have added this code after my textView is populated with the dynamic text:
if let headerView = self.tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
self.tableView.tableHeaderView = headerView
}
}
Which adjusts the size of the header. However, when the textView has less text than the height of the image, the size of the view grows.
Example of three lines of text:
Example of six lines of text:
Example of enough text to pass imageview:
Does anyone know why this is happening?
I have a fix for this, because I just encountered this issue myself :)
You see, I was trying to do something very similar, where a UITextView's text should avoid a UIImageView to its left. My code was the following:
let ticketProfileExclusionPath = UIBezierPath(roundedRect: ticketProfilePicture.frame, cornerRadius: Constants.ProfilePictureExclusionRadius)
ticketContent.textContainer.exclusionPaths.append(ticketProfileExclusionPath)
And my results were not very good:
As you can see, the problem relies on the CGRect given to the ticketContent UITextView as its exclusion path, since the latter assumes the given CGRect is corrected to its own frame, not its superview's.
The fix is very simple and requires the use of an API present since the dawn of time (iOS 2.0):
let exclusionPathFrame = convert(ticketProfilePicture.frame, to: ticketContent).offsetBy(dx: Constants.SystemMargin, dy: Constants.Zero)
What we're doing here is converting the UIImageView's frame to the UITextView's coordinate system, therefore providing the correct exclusion path from the perspective of the UITextView. The added offset is simply to align all three of my UITableViewCell's text UIViews.
convert() is a UIView method.
As you can see, the text now wraps around the ticketProfilePicture UIImageView quite nicely.
Hope this helps.
For anyone using Auto Layout, make sure to add the exclusionPathFrame in viewDidLayoutSubviews().
You can calculate textview text height with content size.
let tempTextview = UITextView(frame: CGRect(x: 50, y: 50, width: 120, height: 250))
tempTextview.text = "Dynamic Text"
let height = tempTextview.contentSize.height
Add you extra top and bottom padding to this height.
height = height + padding
if imageview.frame.size.height > height
{
return imageview.frame.size.height
}
else
{
return height
}

Set root view width equal to dynamic label's width

I'm using XIB to populate tableview of a chat app (messages). It contains a view and a label on it. The label's text continuously changes. How can I find the width of label and then set width of parent view equal to that?
I'm a newbie to swift. So, in this way, is it possible to set height also?
I have set the number of lines to '0' of the uilabel
Assuming that the "Message Text" label is the only subview within the parent view, you could set the parent view's height and width like so:
if let subview = view.subviews.first {
// where `view` is your parent view and `subview` is your text label
view.frame.size.width = subview.frame.width
view.frame.size.height = subview.frame.height
}

How to make UILabel resize to fit text

I have a UILabel that I have layed out in a storyboard centred in the view. It has some initial text "tap to login".
I am changing the value of the label to be the user's name once they have logged in. Once I do this, the label is no longer centered as the UILabel has not changed size.
How can I do this with autolayout in interface builder?
see this scrrenshot
1 first select your label width constraint
2 set the relation Greater than or equal
3 set the default constant value here i set it 10
when you change the label text it change the size of label according to its text. hope it's help :)
Iam guessing the label is not getting height and width, consider giving those constraints and then when your text changes use this method to determine the height and width:
func labelSizeWithString(text: String, maxWidth : CGFloat,numberOfLines : Int) -> CGRect{
let label = UILabel(frame: CGRectMake(0, 0, maxWidth, CGFloat.max))
label.numberOfLines = numberOfLines
label.text = text
label.sizeToFit()
return label.frame
}
You can set the UILabel frame.width bigger, maybe the same width as the screen.
Then you set the
label.textAlignment = NSTextAlignmentCenter
1.showing initial text to label
2.After pressing button changing text, that showing in middle of screen 3.Look at constraints third image, two constraints given 1.top to view 2.horizontally middle in container
Another thing if u given fixed width for the label,change relation as more than equal.[Click width constraint ,see right side top there change the relation

Constraining to center horizontally & vertically in superview prevents me from changing the frame

I have a UIButton within a UIView with two constraints. Both constraints were set in Interface Builder so that the UIButton is centered vertically and horizontally in its superview.
I wanted to add a border to the UIButton and make it rounded. Having followed an answer on SO I was able to get the button partially rounded.
For some reason though, the button is not really round. I assume this is because of my height & width not being equal to each other. Each time I set the width/height of the button to be equal, the autolayout constraints reset it back to 40x34.
I am using the following code to create the rounded button.
self.startButton.clipsToBounds = true
self.startButton.layer.cornerRadius = self.startButton.frame.height / 2
self.startButton.layer.borderWidth = 1
self.startButton.layer.borderColor = self.view.tintColor.CGColor
self.startButton.layer.shadowRadius = 6.0
self.startButton.layer.shadowColor = UIColor.blackColor().CGColor
self.startButton.layer.shadowOffset = CGSizeMake(0.0, 3.0)
self.startButton.layer.shadowOpacity = 0.65
Why does having my button centered vertically and horizontally prevent me from changing its size? I don't understand why I can't set the size of the button and have the constraints re-center it based on the size values I assign to it.
Edit
I have moved the code in to the viewDidLayoutSubviews and then modified the constraints so that both the height and width are constrained to 40x40.
override func viewDidLayoutSubviews() {
self.startButton.clipsToBounds = true
self.startButton.titleLabel?.text = "Start"
self.startButton.layer.cornerRadius = self.startButton.frame.height / 2
self.startButton.layer.borderWidth = 1
self.startButton.layer.borderColor = self.view.tintColor.CGColor
self.startButton.layer.shadowRadius = 6.0
self.startButton.layer.shadowColor = UIColor.blackColor().CGColor
self.startButton.layer.shadowOffset = CGSizeMake(0.0, 3.0)
self.startButton.layer.shadowOpacity = 0.65
}
This partially solves my problem; not fully though. If I do not assign a value to the titleLabel, then the button is a proper round button.
However, if I assign a value to the titleLabel, the button becomes a rounded rect and is not a circle.
The text does not appear in the button though, which confuses me. I need to have the button be a circle, scaling to fit the content it has as well. My button type is set to Custom. I don't know if that has any effect on this as well.
Edit 2
After doing some testing, I discovered i was setting the edge insets to the button. Once I removed the following:
self.startButton.contentEdgeInsets = UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25)
I got the desired effect with #Leo's answer.
You need to add constraints about height & width of your button.
Either fixed width & height,or aspect radio is ok.
If you use aspect radio,set up the corner radius in viewDidLayoutSubviews

Autolayout - intrinsic size of UIButton does not include title insets

If I have a UIButton arranged using autolayout, its size adjusts nicely to fit its content.
If I set an image as button.image, the instrinsic size again seems to account for this.
However, if I tweak the titleEdgeInsets of the button, the layout does not account for this and instead truncates the button title.
How can I ensure that the intrinsic width of the button accounts for the inset?
Edit:
I am using the following:
[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
The goal is to add some separation between the image and the text.
You can get this to work in Interface Builder (without writing any code), by using a combination of negative and positive Title and Content Insets.
Update: Xcode 7 has a bug where you cannot enter negative values in the Right Inset field, but you can use the stepper control next to it to decrease the value. (Thanks Stuart)
Doing this will add 8pt of spacing between the image and the title and will increase the intrinsic width of the button by the same amount. Like this:
You can solve this without having to override any methods or set an arbitrary width constraint. You can do it all in Interface Builder as follows.
Intrinsic button width is derived from the title width plus the icon width plus the left and right content edge insets.
If a button has both an image and text, they’re centered as a group, with no padding between.
If you add a left content inset, it’s calculated relative to the text, not the text + icon.
If you set a negative left image inset, the image is pulled out to the left but the overall button width is unaffected.
If you set a negative left image inset, the actual layout uses half that value. So to get a -20 point left inset, you must use a -40 point left inset value in Interface Builder.
So you provide a big enough left content inset to create space for both the desired left inset and the inner padding between the icon and the text, and then shift the icon left by doubling the amount of padding you want between the icon and the text. The result is a button with equal left and right content insets, and a text and icon pair that are centered as a group, with a specific amount of padding between them.
Some example values:
// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
Why not override the intrinsicContentSize method on UIView? For example:
- (CGSize) intrinsicContentSize
{
CGSize s = [super intrinsicContentSize];
return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}
This should tell the autolayout system that it should increase the size of the button to allow for the insets and show the full text. I'm not at my own computer, so I haven't tested this.
You haven't specified how you're setting the insets, so I'm guessing that you're using titleEdgeInsets because I see the same effect you're getting. If I use contentEdgeInsets instead it works properly.
- (IBAction)ChangeTitle:(UIButton *)sender {
self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
[self.button setTitle:#"Long Long Title" forState:UIControlStateNormal];
}
And for Swift worked this:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let intrinsicContentSize = super.intrinsicContentSize
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
Love U Swift
This thread is a bit old, but I just ran into this myself and was able to solve it by using a negative inset. For example, substitute your desired padding values here:
UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
-desiredRightPadding,
-desiredTopPadding,
-desiredLeftPadding);
Combined with the right autolayout constraints, you end up with an auto-resizing button which contains an image and text! Seen below with desiredLeftPadding set to 10.
You can see that the actual frame of the button doesn't encompass the label (since the label is shifted 10 points to the right, outside the bounds), but we've achieved 10 points of padding between the text and the picture.
I wanted to add a 5pt space between my UIButton icon and the label. This is how I achieved it:
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);
The way contentEdgeInsets, titleEdgeInsets and imageEdgeInsets relate to each other requires a little give and take from each inset. So if you add some insets to the title's left you have to add negative inset on the right and provide some more space (via a positive inset) on the content right.
By adding a right content inset to match the shift of the title insets my text doesn't go outside the bounds of the button.
For Swift 3 based on pegpeg's answer:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let intrinsicContentSize = super.intrinsicContentSize
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
All above did not work for iOS 9+, what i did is:
Add a width constraint (for a minimum width when the button doesn't have any text. The button will auto scale if text is provided)
set the relation to Greater Than or Equal
Now to add a border around the button just use the method:
button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
The option is also available in interface builder. See the Inset. I set left and right to 3. Works like a charm.
The solution I use is to add a width constraint on the button. Then somewhere in initialization, after your text is set, update the width constraint like so:
self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;
Where 8 is whatever your inset is.

Resources