Was wondering if there is a way to set a dynamic space between words inside a UILabel to obtain something like this
| |
|WordA -dynamic_space_to_fill_the_label- WordB|
| |
Where the | represent the borders of my UILabel.
It would be like I had left alignment for WordA and right alignment for WordB.
Any suggestion on how to do this?
Related
This is an extension of my previous question (Get width of a view using in SwiftUI)
I need to implement a layout where number of items per row is determined dynamically, based on their combined width (basically, place items in a row until they no longer fit).
I've been told that using the GeometryReader is a hacky way to do something in a declarative language, which is obviously true.
I have also been directed to this CollectionView-like component https://github.com/Q-Mobile/QGrid but the solution is static as the number of rows and cells per row is determined once, before any components are rendered.
I have no idea how to approach this, so any advice is very valuable for me!
❤️❤️❤️
TL;DR
GeometryReader may be a "hacky" solution, but it is the solution we have at the moment. It is possible to create a solution that reflows a small number of items dynamically, or a large number of items with a delay. My demo code would be unwieldy here, but it sounds like describing my approach may be useful.
Working with what we've got
Behind the scenes, SwiftUI is doing all kinds of optimized constraint solving to layout your views efficiently. In theory, reflowing content like you describe could be part of that constraint solving; in today's SwiftUI, it is not. Therefore, the only way to do what you are describing is some variant of the following:
Let SwiftUI lay everything out based on our data model.
Get the widths that SwiftUI decided on using Geometry reader and preferences/callbacks.
Use these widths to solve our reflow constraints.
Update the data model, which will trigger step 1.
Hopefully, this process converges to a stable layout, rather than entering an endless loop.
My results
After playing around with it, here's what I've gotten so far. You can see that a small number of items (29 in my example) reflow almost instantaneously as the width is changed. With a large number of items (262 in my example), there is a noticable delay. This shouldn't be much of an issue if the content and view width don't change and won't need to be updated frequently. The time is spent almost entirely in step 1, so until we get proper reflow support in SwiftUI, I suspect this is as good as it gets. (In case you're wondering, the vertical scrollview scrolls with normal responsiveness once the reflow is finished.)
My strategy
Essentially, my data model starts with a [String] array and transforms it to a [[String]] array, where each internal array corresponds to one line that will fit horizontally in my view. (Technically it starts with a String that is split on whitespace to form the [String], but in a generalized sense, I've got a collection I want to split into multiple lines.) Then I can lay it out using VStack, HStack, and ForEach.
My first approach was to try to read the widths off the actual views I'm displaying. However, I quickly ran into infinite recursions or weirdly unstable oscillations because it might truncate a Text view (e.g. [Four] [score] [and] [se...]), and then un-truncate once once the reflow changed, back and forth (or just end in a truncated state.
So I decided to cheat. I lay out all the words in a second, invisible horizontal scrollview. This way, they all get to take up as much space as they want, never get truncated, and most importantly, because this layout only depends on the [String] array and not the derived [[String]] array, it can never enter a recursive loop. You may think that laying each view twice (once for measuring width and once for displaying) is inefficient, but I found it to be dozens of times faster than trying to measure the widths from the displayed views, and to produce proper results 100% of the time.
+---------- FIRST TRY - CYCLIC ----------+ +-------- SECOND TRY - ACYCLIC --------+
| | | |
| +--------+ [String] +----------+ | | +-------+ [String] +--------+ |
| | | | | | | |
| | +--------------------------+ | | | v v |
| | | | | | | Hidden +--> Widths +--> [[String]] |
| v v + v | | layout | |
| Display +--> Widths +--> [[String]] | | v |
| layout | | Display |
| | | layout |
+----------------------------------------+ +--------------------------------------+
To read and save the widths, I adapted the GeometryReader/PreferenceKey approach detailed on swiftui-lab.com. The widths are saved in the view model, and updated whenever the number or size of views in the hidden scrollview change. Such a change (or changing the width of the view) then reflows the [String] array to [[String]] based on the widths saved in the model.
Summary
Now, whether any of this is useful in a shipping application will depend on how many items you want to reflow, and whether they will be static once laid out or changing often. But I found it to be a fascinating diversion!
It's a two step process using a GeometryReader.
Measure for each item the width of content(item).
Use measurements to lay items out into rows.
Only problem is that it has to recalculate on each redraw or cache the priorly measured width, which is not necessarily a problem with just a few items though.
I won't post the code here since it uses GeometryReader which is not something the author wants to use.
I am making a UITableView with two labels. I have constrained the secondLabel to be +12 from the firstLabel, meaning it will move along if the firstLabel contains a lot of text. However, that means if the firstLabel contains hardly any text the secondLabel will move back. What can I do to make this work?
I would like to have it react like this:
secondLabel stays in place despite there being very little text in the firstLabel:
_________________________________________________________
| |
| [abc] [secondLabel] |
|________________________________________________________|
secondLabel moves along due to firstLabel having a lot of text to display:
__________________________________________________________
| |
| [extralongfirstLabel] [secondLabel] |
|________________________________________________________|
Both the firstLabel and secondLabel will have a varying amount of text. The firstLabel leading side should be constrained to the SuperView margin.
The secondLabel would maintain a distance of 12 from the leading side of the firstLabel, but then remain 56.5 from the SuperView when not constraining to the firstLabel (when it contains little text).
Both text labels are on the left of the cell, allowing the text to spread across it.
I am trying to complete this task via the IB, but if you have any code suggestions please let me know.
This sounds like job for multiple constraints, one of which has a lower priority.
First, a required (priority 1000) greater-than-or-equal-to constraint to space the second label's leading edge 12 points from the first label's trailing edge.
Second, a lower-priority constraint for the distance between the cell's and the second label's leading edges. This will be broken when the first label becomes too large, which is fine: that's why it's lower priority.
When the second constraint is broken, the first constraint will be the control for the distance between the labels.
So, for first label set its leading constraint equal to superview's with constant how you want to.
Now, for second label set two leading constraints.
One equal to first label's trailing constraint with some static
constant (means relation equal) and with lower priority (set its priority lower than for the second leading constraint, so for example 999)
Second equal to superview's leading constraint with
relation greater than or equal
Constraints:
Result:
So I want to add space between the legend of a multiple line chart. There is only the option to adjust center | left | right
What it looks like now - The labels are aligned according to the "label size":
What I want to look like - It should be 3 colums of 2 labels each Or maybe, add some space or set a minimum size for each legend:
This is the part of the code that enables the legend.
lineChartView.legend.enabled = true
lineChartView.legend.horizontalAlignment = .center
I've tried chartView.legend.stackSpace | chartView.legend.xEntrySpace | chartView.legend.yEntrySpace but nothing seems to change.
Anyone can help ?
There is a property of Chart "extraBottomOffset" and its datatype is CGFloat, try to give some value and see the change
PLease use legend.yEntrySpace = "value" for legend.orientation = .vertical.
I am struggling with autolayout in Interface Builder.
I am having the following layout:
-------------------------------
| |
| _____________________ |
| | | |
|<-->| UILabel |<-->|
| | | |
| --------------------- |
| <-------------------> |
-------------------------------
What I want to accomplish:
I want the UILabel to have a width of 300, unless the left/right margin becomes less then 30. In that case the UILabel's width must be smaller so the left/right margin will always be 30.
What I tried:
UILabel Width: Less then or equal: 300
Left/Right Leading/Trailing: Greater than or equal: 30
This works in layouts that don't fit the 360 width, so the UILabel becomes smaller. However, when I have like 400 width available, the UILabel becomes 280 and the Leading/Trailing becomes 50...
I have experimented with different priorities, but nothing seems to have a real effect. I also tried to set the UILabel width constraint to equal, but that did not work on widths smaller then 360.
Is there a solution to this problem?
Try this... on your label:
set both Leading and Trailing to Greater than or equal: 30
set Width to 300, with Priority of 999
That should do what you need.
Give UILabel a width of 300 and set priority to High.
Left/Right Leading/Trailing : 30 with Required priority.
This should work.
say that I have a string "ABCDEDGHIJK"
what I want is to align it to be like this in a UILabel:
ABC
DEFGHI
while I set the textAlignment to NSTextAlignmentRight ,it got something like:
ABCDEF
GHI
While the whole string is not exactly as long as the length of 2 lines,How can the bottom is filled and leaving the top line to be filled the rest of the word