Single directional (Height only) self sizing UI components preview - xcode

I have a UITableViewCell containing a simple UIStackView and 2 UILabels (So it is from UIKit, NOT native SwiftUI) that should have a static width and dynamic height. How can I have a preview for this without need to see the actual phone size?
Note 1: .sizeThatFits will put all the weight on the width and there will be no multiline labels
Note 2: .device is showing extra useless empty spaces of the main view of the screen.
Note 3: .fixed(width:height:) is not prefered, because it will have less space or extra useless space as our needs.
Note 4: Need something like this: (For UIStackView)
DEMO
struct UILabelPorted: UIViewRepresentable {
var configuration = { (view: UILabel) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> UILabel {
let uiView = UIViewType()
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return uiView
}
func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<Self>) { configuration(uiView) }
}
struct UILabel_Previews: PreviewProvider {
static var previews: some View {
UILabelPorted {
$0.text = "This label should have multiple lines like it is in a UITableView cell."
}
.previewLayout(.sizeThatFits)
}
}

It is not very clear where is the problem, but you need something like
ContentView()
.previewLayout(.fixed(width: 414, height: 300))

Related

Autoresize UILabel within View

Looking for a solution for the following:
struct AttributedParagraph: View {
private let attributedText: NSMutableAttributedString
init (_ text: String) {
self.attributedText = text.attributed // tiny markdown parser
let paragraphStyle = NSMutableParagraphStyle()
...
attributedText.addAttribute(.paragraphStyle, ...)
attributedText.addAttribute(.font, ...)
}
var body: some View {
// Wrap it so padding for example works
Group {
AttributedText(attributedText: self.attributedText)
}
}
}
struct AttributedText: UIViewRepresentable {
var attributedText: NSMutableAttributedString
func makeUIView(context: Context) -> UILabel {
return UILabel()
}
func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<AttributedText>) {
uiView.attributedText = self.attributedText
uiView.lineBreakMode = .byWordWrapping
uiView.numberOfLines = 0
uiView.sizeToFit()
}
}
How do I make AttributedParagraph to behave like Text in SwiftUI? At the moment the UILabel ignores all constraints passed from container and produces one long line. Ideally I'd like UILable to automatically break the lines whilst respecting the width provided by AttributedParagraph, and adjust its height to fit the content.

How do I make my SwiftUI UIViewRepresentable respect intrinsicContentSize in previews?

When I create a view in SwiftUI and render it in an Xcode preview, using PreviewLayout.sizeThatFits, the preview adjusts its size according to its content. When I import a UIKIt view using UIViewRepresentable, it appears with a full device-size frame.
Is there a way to make SwiftUI respect the intrinsicContentSize of UIView subclasses?
struct Label: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<Label>) -> UILabel {
return UILabel()
}
func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<Label>) {
uiView.text = "Some text"
}
}
#if DEBUG
struct Label_Previews: PreviewProvider {
static var previews: some View {
Group {
Label().previewLayout(.sizeThatFits)
}
}
}
#endif
Add the following to your updateUIView function:
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
You can also limit the UIViewRepresentable size from the SwiftUI side.
For this you can use fixedSize:
struct Label_Previews: PreviewProvider {
static var previews: some View {
Label()
.fixedSize()
.previewLayout(.sizeThatFits)
}
}
You can also fix the view size in one dimension only:
.fixedSize(horizontal: false, vertical: true)

NSCollectionView Scroll to bottom

I have an NSCollectionView which consists of dozens of rows of single-columned items (chat messages) in a messaging application.
Each item contains a text area of which the heights vary. Therefore the view should be defaulted to the bottom when the view is created and scrolled to the bottom when new messages are received.
I am struggling to either default the scroll to the bottom or work out how to get the height of the CollectionView's contents to scroll to the bottom.
Any help would be greatly appreciated, thanks!
If you support only macOS 10.11 and up, and if you use a NSCollectionViewLayout, you could ask the layouter for the size of the entire content. That's also how the scrollbars are getting sized.
So, in ObjC, you'd simply ask your collectionView for its layouter's content size and scroll to the bottom:
NSSize contentSize = theCollectionView.collectionViewLayout.collectionViewContentSize.height;
[theCollectionView.enclosingScrollView.contentView scrollPoint:NSMakePoint(0, contentSize.height)];
hi i suggest you to work on Offset for horizontal UICollectionView and Apple provided a method for bottom for vertical UICollectionView
UICollectionView Horizontal Next and Previous Item
extension UICollectionView {
func scrollToNextItem() {
let contentOffset = CGFloat(floor(self.contentOffset.x + self.bounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func scrollToPreviousItem() {
let contentOffset = CGFloat(floor(self.contentOffset.x - self.bounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func moveToFrame(contentOffset : CGFloat) {
let frame: CGRect = CGRect(x: contentOffset, y: self.contentOffset.y , width: self.frame.width, height: self.frame.height)
self.scrollRectToVisible(frame, animated: true)
}
}
UICollectionView Vertical Scroll to Bottom
extension UICollectionView {
func scrollToBottom(animated: Bool) {
let sections = self.numberOfSections
if sections > 0 {
let rows = self.numberOfItems(inSection: sections - 1)
let last = IndexPath(row: rows - 1, section: sections - 1)
DispatchQueue.main.async {
self.scrollToItem(at: last, at: .bottom, animated: animated)
}
}
}
}
UITableView Scroll to Bottom
extension UITableView {
func scrollToBottom(animated: Bool) {
let sections = self.numberOfSections
if sections > 0 {
let rows = self.numberOfRows(inSection: sections - 1)
let last = IndexPath(row: rows - 1, section: sections - 1)
DispatchQueue.main.async {
self.scrollToRow(at: last, at: .bottom, animated: animated)
}
}
}
}
Usage
self.collectionView.scrollToBottom(animated: true)
self.tableView.scrollToBottom(animated: true)

Custom TableViewCell with Subview whose height changes

I'm using a TableView and have a custom TableViewCell that I've added a subview to.
The problem is that I need the subview's height to change sometimes and therefore, the table's contentView would have to be updated as well as the row's height.
The subview of the custom TableViewCell is represented by the yellow background.
These images show what's currently happening in my simulator.
On Load
After the event that causes the subview's height to increase
What's the best approach to take with something like this?
Should I use constraints? And if so, what kind of constraints should I use? Would I have to then reload the tableview too every time the subview's size changes?
Here is the code I'm currently using for my custom TableViewCell:
import UIKit
class CustomCell: UITableViewCell {
var newView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.newView = UIView(frame: self.frame)
self.newView.backgroundColor = .yellowColor()
self.addSubview(newView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.newView.frame.size.width = self.frame.size.width // because self.frame.width is different than it was in the init method
}
func somethingHappenedThatMySubviewHasToIncreaseInHeight() {
self.newView.frame.size.height = self.frame.size.height + 40
}
}
The best approach is to use Auto Layout and self-sizing cells. Setup constraints in storyboard for your custom cell.
You will not need to reload the tableView. Each cell will automatically adjust its height, based on how much vertical space its subview takes.
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.

Using Autolayout with expanding NSTextViews

My app consists of an NSScrollView whose document view contains a number of vertically stacked NSTextViews — each of which resizes in the vertical direction as text is added.
Currently, this is all managed in code. The NSTextViews resize automatically, but I observe their resizing with an NSViewFrameDidChangeNotification, recalc all their origins so that they don't overlap, and resize their superview (the scroll view's document view) so that they all fit and can be scrolled to.
This seems as though it would be the perfect candidate for autolayout! I set NSLayoutConstraints between the first text view and its container, the last text view and its container, and each text view between each other. Then, if any text view grows, it automatically "pushes down" the origins of the text views below it to satisfy contraints, ultimately growing the size of the document view, and everyone's happy!
Except, it seems there's no way to make an NSTextView automatically grow as text is added in a constraints-based layout? Using the exact same NSTextView that automatically expanded as text was entered before, if I don't specify a constraint for its height, it defautls to 0 and isn't shown. If I do specify a constraint, even an inequality such as >=20, it stays stuck at that size and doesn't grow as text is added.
I suspect this has to do with NSTextView's implementation of -intrinsicContentSize, which by default returns (NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric).
So my questions: if I subclasses NSTextView to return a more meaningful intrinsicContentSize based on the layout of my text, would my autolayout then work as expected?
Any pointers on implementing intrinsicContentSize for a vertically resizing NSTextView?
I am working on a very similar setup — a vertical stack of views containing text views that expand to fit their text contents and use autolayout.
So far I have had to subclass NSTextView, which is does not feel clean, but works superbly in practice:
- (NSSize) intrinsicContentSize {
NSTextContainer* textContainer = [self textContainer];
NSLayoutManager* layoutManager = [self layoutManager];
[layoutManager ensureLayoutForTextContainer: textContainer];
return [layoutManager usedRectForTextContainer: textContainer].size;
}
- (void) didChangeText {
[super didChangeText];
[self invalidateIntrinsicContentSize];
}
The initial size of the text view when added with addSubview is, curiously, not the intrinsic size; I have not yet figured out how to issue the first invalidation (hooking viewDidMoveToSuperview does not help), but I'm sure I will figure it out eventually.
I had a similar problem with an NSTextField, and it turned out that it was due to the view wanting to hug its text content tightly along the vertical orientation. So if you set the content hugging priority to something lower than the priorities of your other constraints, it may work. E.g.:
[textView setContentHuggingPriority:NSLayoutPriorityFittingSizeCompression-1.0 forOrientation:NSLayoutConstraintOrientationVertical];
And in Swift, this would be:
setContentHuggingPriority(NSLayoutConstraint.Priority.fittingSizeCompression, for:NSLayoutConstraint.Orientation.vertical)
Here is how to make an expanding NSTextView using Auto Layout, in Swift 3
I used Anchors for Auto Layout
Use textDidChange from NSTextDelegate. NSTextViewDelegate conforms to NSTextDelegate
The idea is that textView has edges constraints, which means whenever its intrinsicContentSize changes, it will expand its parent, which is scrollView
import Cocoa
import Anchors
class TextView: NSTextView {
override var intrinsicContentSize: NSSize {
guard let manager = textContainer?.layoutManager else {
return .zero
}
manager.ensureLayout(for: textContainer!)
return manager.usedRect(for: textContainer!).size
}
}
class ViewController: NSViewController, NSTextViewDelegate {
#IBOutlet var textView: NSTextView!
#IBOutlet weak var scrollView: NSScrollView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
activate(
scrollView.anchor.top.constant(100),
scrollView.anchor.paddingHorizontally(30)
)
activate(
textView.anchor.edges
)
}
// MARK: - NSTextDelegate
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
print(textView.intrinsicContentSize)
textView.invalidateIntrinsicContentSize()
}
}
Class ready for copying and pasting. Swift 4.2, macOS 10.14
class HuggingTextView: NSTextView, NSTextViewDelegate {
//MARK: - Initialization
override init(frame: NSRect) {
super.init(frame: frame)
delegate = self
}
override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
super.init(frame: frameRect, textContainer: container)
delegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
delegate = self
}
//MARK: - Overriden
override var intrinsicContentSize: NSSize {
guard let container = textContainer, let manager = container.layoutManager else {
return super.intrinsicContentSize
}
manager.ensureLayout(for: container)
return manager.usedRect(for: container).size
}
//MARK: - NSTextViewDelegate
func textDidChange(_ notification: Notification) {
invalidateIntrinsicContentSize()
}
}

Resources