UITextView Height Adjustment - swift

I'm trying to create a ViewController which will hold some large text. I used UILabel but since the text can be long, it won't work. Trying to switch to UITextViewbut I'm having some problems.
I'm using ScrollView since there is a image on the top and button on the bottom. So I don't it scrolling inside the UITextView itself. I've disabled it.
I want to Text to height itself automatically according to the text length. So it can scroll with the images, buttons etc. Just like the apps Instapaper, Medium, Pocket etc.
I've tried all the code and solutions on StackOverflow, but they either didn't work or they were Objective-C.
Storyboard Structure:
View Controller -> View -> ScrollView -> Image, TextView, Button
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
var theContent = fromClass.text
override func viewDidLoad() {
super.viewDidLoad()
textView.text = theContent
}
}

There is few ways to change the size of the UITextView
extension String {
func bounds(approximated width: CGFloat, approximated height: CGFloat, font: UIFont) -> CGRect {
let size = CGSize(width: width, height: height)
let attribs = [NSAttributedStringKey.font: font]
return NSString(string: self).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attribs, context: nil)
}
}
approximated width = width of your UITextView I assume it is going to be static value which you won't change.
approximated height = minimal height of your UITextView.
The method going to return frame of your text input. Take from it height property and depending of how big is that change the size of the UITextView.
If it won't satisfy you, you can use another way.
func heightOfString(_ attributes: [NSAttributedKeyString : Any]) -> CGFloat {
let size = self.size(withAttributes: attributes)
return size.height
}
The same usage as from above.

Adjust your view hierarchy to the following view hierarchy :
View Controller -> View -> ScrollView -> View1 -> Image, UILabel, Button
1- give view1 top-left-bottom-right constraints and and align it center X to it's parent view
2- add UIImageView in the top of View1 and give it top-left-right constraint and a fixed height constraint.
3- add a UILabel and give it top constraint to the image and right-bottom-left constraint to it's superview.
make UILabel number of lines = 0
The content not should be scrollable.
Note that: in your case; you do not need UITextView .
If you want to use UITextView:
1- add a height constraint to the UITextView and connect an outlet to it
called: constTextViewHeight
let textViewContentHeight = textView.contentSize.height//call this line after adding the text to the textView
self.constTextViewHeight.contant = textViewContentHeight
self.view.layoutIfNeeded()

Related

How to expand height of containing view as TextField expands vertically with a newline

I have an array of Views that expand vertically in a scrollView. The views are of equal width and lined up top to bottom. Each View has a multiline text field that begins at a fixed height, and gets taller as lines are added. How can I constrain the height of the parent view to the height of the text field? Is it possible to do something like this (constraining a view within itself)?
Class DocCell: NSView {
// removed unnecessary components
var textField: NSTextField!
override func updateConstraints() {
NSLayoutConstraint.activate([
heightAnchor.constraint(equalTo: textField.heightAnchor, constant: 10),
// ..other constraints
])
}
}
The goal is that I don't have to update the height of each view every time the textField adds a new line, and that the size of the containing view just expands with the textField.
The goal is that I don't have to update the height of each view every time the textField adds a new line, and that the size of the containing view just expands with the textField.
You have no choice. You need to update the height of the field each time a new line is added. You can, for example, override textDidChange: in the NSTextField subclass, and call invalidateInstrinsicContentSize (after calling super) and recalculate the size there:
override func textDidChange(_ note: Notification) {
super.textDidChange(note)
invalidateIntrinsicContentSize()
}
override var intrinsicContentSize: NSSize {
let cell = self.cell as! NSTextFieldCell
var boundingRect = frame
boundingRect.size.height = CGFloat.max
return cell.cellSize(for: boundingRect)
}
This will let your other existing constraints do the right thing as long as you have the hugging and compression resistance set to 1000.

Automatic height adjustment for static UITableViewCell doesn't work

I have a static UITableView and I want to set the row height for three of the cells dynamically. So in viewDidLoad() I implemented the following code:
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
I also implemented the heightForRowAt method:
(The first two cells of the first section should have a fixed height)
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 0 {
return CGFloat(85)
} else if indexPath.section == 0 && indexPath.row == 1 {
return CGFloat(145)
}
return UITableView.automaticDimension
}
This the result which I'm currently getting:
I changed the lines of the labels to 0, too and the constraints of the labels inside the cells are 0, 12, 0, 12 (top, right, bottom, left).
Does anybody know, why the cell in section 3 doesn't display the data in the right way?
Edit:
(How it looks after the implementation of the suggestion above)
Because sizeToFit() did not work for you, we are going to try something a little more involved.
The cell in section 3 is displaying the data the right way. This is because UILabels don't automatically adjust their height to accommodate the text inside. Here's what you need to do:
1. Create a height constraint for your UILabel In your interface builder, add a constraint for the height of the UILabel in section 3's cell. Connect this height constraint to your view controller's class via an #IBOutlet:
class YourViewController: UIViewController {
#IBOutlet var cellLabel: UILabel!
#IBOutlet var cellLabelHeight: NSLayoutConstraint!
...
}
2. Add String extension that calculates height I am unsure of where/when you are setting the text of the UILabel in question, but I know you are doing this somewhere as you have described it as being "dynamic". Whenever you do set the text of the UILabel in question, you now also need to change the constant of the height constraint that we made in order to accommodate this text. So, we need to be able to calculate the height of the UILabel based on its width and font. We can add an extension to String in order to do this:
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
}
}
3. Set the height constraint's constant based off the UILabel's text The final step is to set the height of the UILabel height constraint we made by using the extension we just created:
cellLabel.text = "DummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyData"
//This will be called immediately after you set the text for the UILabel in question
cellLabelHeight.constant = cellLabel.text.height(withConstrainedWidth: cellLabel.frame.width, font: cellLabel.font)
The cell in section 3 is displaying the data the right way. Unless you tell it otherwise, a UILabel will not automatically adjust to accommodate the text within it.
What I need you to do is select the UILabel in question, then in the attributes inspector, set the Number of Lines to 0.
You also said that this UILabel is dynamic, meaning you are setting it's text somewhere in your code. Immediately after you set this UILabel's text, you are going to want to call myLabel.sizeToFit(). This should adjust the label's height to accommodate the text within.
If this doesn't work, I have another, more involved solution that should work for you.
Please look at the below;
Select your cellLabel and set the Lines value to 0:
Also apple says Self-Sizing
Summary :
lay out your table view cell’s content within the cell’s content view. To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge. If your views have intrinsic content heights, the system uses those values. If not, you must add the appropriate height constraints, either to the views or to the content view itself.
Change the bottom constraint of the AuthorLabel from equal to Greater than or equal

Swift text field border isn't the right width

I have a bottom border that I generated after following the answer here.
This works absolutely great except the border isn't the right width. It's set with constraints to match the width of the button below it but as you can see is coming up short.
What am I missing?
Code :
extension UITextField
{
func setBottomBorder(withColor color: UIColor)
{
self.borderStyle = UITextBorderStyle.none
self.backgroundColor = UIColor.clear
let width: CGFloat = 3.0
let borderLine = UIView(frame: CGRect(x: 0, y: self.frame.height - width, width: self.frame.width, height: width))
borderLine.backgroundColor = color
self.addSubview(borderLine)
}
}
then in the VC :
override func viewDidLoad() {
authorNameOutlet.setBottomBorder(withColor: UIColor.lightGray)
}
Then Xcode shows...
but the simulator shows...
I've tried this both setting the width of the text field to be 0.7 x the superview width (same as the button below it) and also setting the width of the text field to be equal width of the button but neither works.
This is because of AutoLayout.
You can add autoresizingMask to your line.
borderLine.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
You are working with with a static frame for the border line view. After viewDidLoad your view controller's view gets resized.
Option 1: (Fast and dirty)
Move your code from viewDidLoad() to viewWillAppear(_ animated: Bool). viewWillAppear gets called after the first layout of your view controller's view
Option 2:
Add constraint for your border line view. So that your border line view will resize automatically.
Importent hint:
Do not forget super calls in overrides or you will get strange bugs!
E.g:
override func viewDidLoad() {
super.viewDidLoad()
// your code
}

Dragging NSSplitView divider does not resize views

I'm working with Cocoa and I create my views in code (no IB) and I'm hitting an issue with NSSplitView.
I have a NSSplitView that I configure in the following way in my view controller, in Swift:
override func viewDidLoad() {
super.viewDidLoad()
let splitView = NSSplitView()
splitView.isVertical = true
splitView.addArrangedSubview(self.createLeftPanel())
splitView.addArrangedSubview(self.createRightPanel())
splitView.adjustSubviews()
self.view.addSubview(splitView)
...
}
The resulting view shows the two subviews and the divider for the NSSplitView, and one view is wider than the other. When I drag the diver to change the width, as soon as I release the mouse, the divider goes back to its original position, as if pulled back by a "spring".
I can't resize the two subviews; the right one always keeps a fixed size. However, nowhere in the code I fix the width of that subview, or any of its content, to a constant.
What I would like to achieve instead is that the right view size is not fixed, and that if I drag the divider at halfway through, the subviews will resize accordingly and end up with the same width.
This is a screen recording of the problem:
Edit: here is how I set the constraints. I'm using Carthography, because otherwise setting constraints in code is extremely verbose beyond the most simple cases.
private func createLeftPanel() -> NSView {
let view = NSView()
let table = self.createTable()
view.addSubview(table)
constrain(view, table) { view, table in // Cartography magic.
table.edges == view.edges // this just constraints table.trailing to
// view.trailing, table.top to view.top, etc.
}
return view
}
private func createRightPanel() -> NSView {
let view = NSView()
let label = NSTextField(labelWithString: "Name of item")
view.addSubview(label)
constrain(view, label) { view, label in
label.edges == view.edges
}
return view
}

Custom views in Horizontal scroll view. Is it possible?

I am designing an app which has a screen in which I have a horizontal scroll view which I fill with UIViews dynamically depending upon the number of data I have in my array . I did the same via programmatically. I have mentioned my approach below.
1) I put a Scroll view for scrolling horizontally and created a reference for that in my class.
2) I programatically added views as per my code -
var imagevieww = UIImageView()
#IBOutlet weak var hrzntlscrl: UIView!
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad()
{
super.viewDidLoad()
let viewcount = 15
for var i = 0; i < viewcount; i++
{
let viewnew = UIView(frame: CGRectMake( hrzntlscrl.frame.origin.x+110*CGFloat(i), 0, 100.0, hrzntlscrl.frame.height))
viewnew.backgroundColor = UIColor.orangeColor()
imagevieww = UIImageView(frame: CGRectMake(0, 10, 100.0, 50))
imagevieww.backgroundColor = UIColor.blackColor()
viewnew.addSubview(imagevieww)
scrollview.addSubview(viewnew)
}
}
So I just wanted to know that instead of creating a view and the corresponding subviews eg. here imageview and setting their location and frame size programatically , Can I have a standard custom view designed in my IB and use any reference of that in my for loop instead of creating one programmatically? If we can do that,can you please give me some steps.
Yes. This is possible. You can instantiate a class from a nib with
let customView: CustomView = NSBundle.mainBundle().loadNibNamed("CustomViewNibName", owner: self, options: nil)[safe: 0] as? CustomView
You would also need to set the content size of the horizontal scroll view to the combined width of all the views.
But I think that your use case would be better served by using a UICollectionView instad of a scroll view, UICollectionView does all this and more in a much simpler implementation.