Automatically adjustable view height based on text height in SwiftUI
Here is a solution based on view preference key. Tested with Xcode 11.4 / iOS 13.4
struct DynamicallyScalingView: View {
@State private var labelHeight = CGFloat.zero // << here !!
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.font(.system(size: 32))
.padding(20)
.frame(minHeight: labelHeight) // << here !!
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
.background(GeometryReader { // << set right side height
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
}
.onPreferenceChange(ViewHeightKey.self) { // << read right side height
self.labelHeight = $0 // << here !!
}
.padding(.horizontal)
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
Dynamic Height of Rectangle as from text content in SwiftUI
From what I understood of your question is that:
- You have an
HStack
in which the leftmost view is aRectangle
and the rightmost view is aText
. - You want the
Rectangle
to be the same height as theText
.
The problem is that the height of the HStack
is based on the tallest child view which happens to be the Rectangle
but a Rectangle
view does not have any intrinsic size like Text
and will occupy all the space the parent provides, or if you manually apply a frame.
You set a width of 20 but leave height and so it takes the entire height it can get.
This indicates that we need to set the height of the Rectangle
to be same as the dynamic Text
but the problem is that we don't know the height upfront.
To solve this:
- First we need to know the height of the dynamic
Text
.- For this we will use
GeometryReader
and access the height value.
- For this we will use
- The height is in a child view so we need it to notify the parent it's height value.
- For this we will use
PreferenceKey
- For this we will use
- The parent view should update the
Rectangle
when it gets to know theText
height- A simple
@State
variable will suffice now
- A simple
Solution:
struct ContentLengthPreference: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct ContentView: View {
@State var textHeight: CGFloat = 0 // <-- this
var body: some View {
HStack {
Rectangle()
.frame(width: 20, height: textHeight) // <-- this
Text(String(repeating: "lorem ipsum ", count: 25))
.overlay(
GeometryReader { proxy in
Color
.clear
.preference(key: ContentLengthPreference.self,
value: proxy.size.height) // <-- this
}
)
}
.onPreferenceChange(ContentLengthPreference.self) { value in // <-- this
DispatchQueue.main.async {
self.textHeight = value
}
}
}
}
- Create
ContentLengthPreference
as ourPreferenceKey
implementation - on
Text
; Applyoverlay
containingGeometryReader
overlay
will have same height asText
- in
GeometryReader
,Color.clear
is just a filler invisible view anchorPreference
modifier allows us to access and store heightonPreferenceChange
modifier on parentHStack
can catch the value passed by child view- parent saves the height to a state property
textHeight
textHeight
can be applied onRectangle
and will update the view when this value updates
Credits: https://www.wooji-juice.com/blog/stupid-swiftui-tricks-equal-sizes.html
Output (including your header + footer views):
EDIT:
If you have multiple of these in a List
then you don't need to do anything. Each row will size automatically upto the Text
height.
It's free!!!
struct ContentView: View {
var body: some View {
List(0..<20) { _ in
ArticleView()
}
}
}
struct ArticleView: View {
var body: some View {
VStack(alignment: .leading) {
HStack {
Circle()
.fill(Color.blue)
.frame(width: 15, height: 15)
.overlay(Circle().inset(by: 2).fill(Color.white))
Text("Headline").font(.headline)
}
HStack {
Rectangle().frame(width: 20)
Text(String(repeating: "lorem ipsum ", count: (5...50).randomElement()!))
}
HStack {
Circle()
.fill(Color.orange)
.frame(width: 15, height: 15)
.overlay(Circle().inset(by: 2).fill(Color.white))
Text("Footer").font(.subheadline)
}
}
}
}
Automatic adjust font size in Text() of Swiftui?
Use minimumScaleFactor(_:)
https://developer.apple.com/documentation/swiftui/text/minimumscalefactor(_:)
struct ContentView: View {
var body: some View {
VStack{
Text("Test string")
.minimumScaleFactor(0.1) //<--Here
.frame(width: 15, height: 15)
}
}
}
Make a View the same size as another View which has a dynamic size in SwiftUI
As @Asperi pointed out, this solves the problem: stackoverflow.com/a/62451599/12299030
This is how I solved it in this case:
import SwiftUI
struct TestScreen: View {
@State private var imageHeight = CGFloat.zero
var body: some View {
VStack {
HStack {
Image("blue-test-image")
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(
key: ViewHeightKeyTestScreen.self,
value: $0.frame(in: .local).size.height
)
})
VStack {
Text("Some Text")
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
Text("Other Text")
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
.frame(maxWidth: .infinity, minHeight: self.imageHeight, maxHeight: self.imageHeight)
}
.onPreferenceChange(ViewHeightKeyTestScreen.self) {
self.imageHeight = $0
}
Spacer()
}
.padding()
}
}
struct ViewHeightKeyTestScreen: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
struct TestScreen_Previews: PreviewProvider {
static var previews: some View {
TestScreen()
}
}
Align heights of HStack members
Wrap your Image
in a Text
. Since your image is from SF Symbols, SwiftUI will scale it to match the dynamic type size. (I'm not sure how it will scale other images.)
VStack {
let background = RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
ForEach(Font.TextStyle.allCases, id: \.self) { style in
HStack {
Text("\(style)" as String)
.padding()
.background(background)
Spacer()
Text(Image(systemName: "checkmark.seal.fill"))
.padding()
.background(background)
}
.font(.system(style))
}
}
SwiftUI: How to change the TextField height dynamicly to a threshold and then allow scrolling?
Here is a way for this issue:
struct ContentView: View {
@State private var commitDescr: String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
var body: some View {
TextEditorView(string: $commitDescr)
}
}
struct TextEditorView: View {
@Binding var string: String
@State private var textEditorHeight : CGFloat = CGFloat()
var body: some View {
ZStack(alignment: .leading) {
Text(string)
.lineLimit(5)
.foregroundColor(.clear)
.padding(.top, 5.0)
.padding(.bottom, 7.0)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
TextEditor(text: $string)
.frame(height: textEditorHeight)
}
.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
Dynamic row hight containing TextEditor inside a List in SwiftUI
You can use an invisible Text
in a ZStack
to make it dynamic.
struct ContentView: View {
@State var text: String = "test"
var body: some View {
List((1...10), id: \.self) { _ in
ZStack {
TextEditor(text: $text)
Text(text).opacity(0).padding(.all, 8) // <- This will solve the issue if it is in the same ZStack
}
}
}
}
Note that you should consider changing font size and other properties to match the TextEditor
How to scale text to fit parent view with SwiftUI?
One can use GeometryReader
in order to make it also work in landscape mode.
It first checks if the width or the height is smaller and then adjusts the font size according to the smaller of these.
GeometryReader{g in
ZStack {
Circle().strokeBorder(Color.red, lineWidth: 30)
Text("Text")
.font(.system(size: g.size.height > g.size.width ? g.size.width * 0.4: g.size.height * 0.4))
}
}
Related Topics
Ios: Launch Image Multiple Language
Smart-Search for Parse Usernames in Swift Not Working
Uitableview Dynamic Cell Heights Only Correct After Some Scrolling
Warning: the Copy Bundle Resources Build Phase Contains This Target's Info.Plist File
Prevent Dismissal of Modal View Controller in Swiftui
Programmatically Switching Between Tabs Within Swift
How to Keep a Round Imageview Round Using Auto Layout
(Swift) Storing and Retrieving Array to Nsuserdefaults
Cannot Use Instance Member Within Property Initializer
Exc_Bad_Access Error When Trying to Change Bool Property
Exc_Bad_Instruction When Passing Uicollectionview Cell Data to Different Viewcontroller
Use Didselectrowatindexpath or Prepareforsegue Method for Uitableview
Uitapgesturerecognizer Tap on Self.View But Ignore Subviews
Distancefromlocation - Calculate Distance Between Two Points
Crashlytics in iOS Won't Proceed Past "Build Your Project" in Fabric App
Linker Error in iOS (Duplicate Symbols for Architecture X86_64)