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
Controlling size of TextEditor in SwiftUI
You can use a PreferenceKey
and an invisible Text
overlay to measure the string dimensions and set the TextEditor
's frame:
struct TextEditorView: View {
@Binding var string: String
@State var textEditorHeight : CGFloat = 20
var body: some View {
ZStack(alignment: .leading) {
Text(string)
.font(.system(.body))
.foregroundColor(.clear)
.padding(14)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
TextEditor(text: $string)
.font(.system(.body))
.frame(height: max(40,textEditorHeight))
.cornerRadius(10.0)
.shadow(radius: 1.0)
}.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
Adapted from my other answer here: Mimicking behavior of iMessage with TextEditor for text entry
Make TextEditor dynamic height SwiftUI
I found a solution!
For everyone else trying to solve this:
I added a Text
with the same width of the input field and then used a GeometryReader
to calculate the height of the Text which automatically wraps. Then if you divide the height by the font size you get the number of lines.
You can make the text field hidden (tested in iOS 14 and iOS 15 beta 3)
Dynamic height for TextEditor
You can put it in a ZStack
with an invisible Text
for sizing:
ZStack {
TextEditor(text: $text)
Text(text).opacity(0).padding(.all, 8) // <- This will solve the issue if it is in the same ZStack
}
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)
}
}
}
}
Dynamic content hight in VStack SwiftUI
ScrollView
Solved was putt all view in ZStack and add some fake views in Scroll view.
1 view is responsible for the size where the scroll cannot fall through.
2 view is responsible for the size between the maximum and minimum size of the header
Here's a examples
import Introspect
struct ContentView: View {
@StateObject private var myCoord = MyCoord()
var body: some View {
let sizeOfPlaceholder = myCoord.maxSize - myCoord.minSize
ZStack(alignment: .top) {
VStack(spacing: 0) {
Color.black
.frame(height: myCoord.minSize)
ScrollView {
Color.brown
.frame(height:sizeOfPlaceholder)
ForEach(0..<20) { number in
Text("\(number)")
.background(Color.red)
.frame(height: 20)
}
}
.padding(.vertical, 1)
.introspectScrollView { scroll in
scroll.delegate = myCoord
}
}
Color.green
.frame(height: myCoord.height)
}
}
}
class MyCoord: NSObject, UIScrollViewDelegate, ObservableObject {
let maxSize: CGFloat = 76
let minSize: CGFloat = 56
@Published var height: CGFloat = 76
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.y)
let size = maxSize - scrollView.contentOffset.y
height = min(maxSize, max(minSize, size))
}
}
Related Topics
@Noescape Attribute in Swift 1.2
How to Check If a Property Value Exists in Array of Objects in Swift
How to Convert Timeinterval into Minutes, Seconds and Milliseconds in Swift
Making Simple Accordion Tableview in Swift
Get Subdirectories Using Swift
Using Combine's Future to Replicate Async Await in Swift
Using Some Protocol as a Concrete Type Conforming to Another Protocol Is Not Supported
Convert Firebase Firestore Timestamp to Date (Swift)
Deep Copy for Array of Objects in Swift
Why Non Optional Any Can Hold Nil
Extension of Dictionary Where <String, Anyobject>
Swift & Firebase | Checking If a User Exists with a Username
Checking for Nil Value in Swift Dictionary Extension
Firebase: How to Update Multiple Nodes Transactionally? Swift 3