SwiftUI: Make sheet show content header or complete content - ios

I'd like to have a SwiftUI sheet that either shows the header or complete content.
Requiring iOS 16 is ok.
I already get the correct two measured heights a presentationDetents
import Foundation
import SwiftUI
struct ContentView: View {
#State private var showSheet = false
#State private var headerSize: CGSize = .zero
#State private var overallSize: CGSize = .zero
var body: some View {
Button("View sheet") {
showSheet = true
}
.sheet(isPresented: $showSheet) {
Group {
VStack(alignment: .leading) {
Text("Header")
.background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: HeaderSizePreferenceKey.self, value: geometryProxy.size)
}
)
Text("")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
} // VStack
.padding()
.background(
//measure without spacer
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
Spacer()
} // Group
.onPreferenceChange(SizePreferenceKey.self) { newSize in
overallSize.height = newSize.height
}
.onPreferenceChange(HeaderSizePreferenceKey.self) { newSize in
headerSize.height = newSize.height
}
.presentationDetents([
.height(headerSize.height),
.height(overallSize.height)
]
)
} // sheet content
}
}
struct HeaderSizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
}
struct MySheet_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This code is based on ideas from Make sheet the exact size of the content inside
It almost works. This is the complete content size:
This is the header content size:
What can I do that the last case shows the top of the content ("Header") instead of the center of the content?

So you want this:
The sheet content's intrinsic height includes both the header and the body (the “Some very long text” lines). The problem to solve: when the sheet is at the header detent, the content doesn't fit in the height, and draws itself center-aligned. One way to solve this is to put the content inside a container that draws its children top-aligned.
I tried ZStack(alignment: .top), and I tried adding a .frame(alignment: .top) modifier, but neither worked. I discovered that GeometryReader does what we need.
I also restructured the sheet content so the header's height is measured including vertical padding.
struct ContentView: View {
#State private var showSheet = false
var body: some View {
Button("View sheet") {
showSheet = true
}
.sheet(isPresented: $showSheet) {
SheetContent()
}
}
}
struct SheetContent: View {
#State private var heights = HeightRecord()
var body: some View {
// Outermost GeometryReader exists only to draw its content top-aligned instead of center-aligned.
GeometryReader { _ in
// spacing: 0 here so only the standard padding separates the
// header from the body.
VStack(alignment: .leading, spacing: 0) {
Text("Header")
.padding([.top, .bottom])
.recordHeight(of: \.header)
// Standard spacing here for the body's subviews.
VStack(alignment: .leading) {
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
}
}
// No top padding here because the header has top padding.
.padding([.leading, .trailing, .bottom])
.recordHeight(of: \.all)
}
.onPreferenceChange(HeightRecord.self) {
heights = $0
}
.presentationDetents([
.height(heights.header ?? 10),
.height(heights.all ?? 10)
])
}
}
struct HeightRecord: Equatable {
var header: CGFloat? = nil
var all: CGFloat? = nil
}
extension HeightRecord: PreferenceKey {
static var defaultValue = Self()
static func reduce(value: inout Self, nextValue: () -> Self) {
value.header = nextValue().header ?? value.header
value.all = nextValue().all ?? value.all
}
}
extension View {
func recordHeight(of keyPath: WritableKeyPath<HeightRecord, CGFloat?>) -> some View {
return self.background {
GeometryReader { g in
var record = HeightRecord()
let _ = record[keyPath: keyPath] = g.size.height
Color.clear
.preference(
key: HeightRecord.self,
value: record)
}
}
}
}

I had the same issue. I solved this by using a ScrollView with no axes.
ScrollView([]) {
// Content here
}
.ignoreSafeArea()

Related

SwiftUI PreferenceKey not Updating

I'm struggling trying to implement a PreferenceKey. I've read countless articles, so
obviously I'm missing something. onPreferenceChange reports once at simulator startup but
not during scrolling. Scrolling updates the text in the TextEditor overlay, but the
offsetCGSize property is apparently never updated. Here's a simplified version
of the code:
struct SOView: View {
#State private var firstText: String = "first"
#State private var secondText: String = "second"
#State private var thirdText: String = "third"
#State private var offsetCGSize: CGSize = .zero
var body: some View {
ScrollView {
VStack {
ForEach(0..<20) {index in
Text("Line number \(index)")
}
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
TextField("First", text: $firstText)
GeometryReader { teGeo in
TextEditor(text: $secondText)
.frame(height: 100)
.overlay(
RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 2)
)
.overlay(
Text("\(teGeo.frame(in: .global).minY)")
)
.preference(key: OffsetPreferenceKey.self, value: CGSize(width: 0, height: teGeo.frame(in: .global).minY))
}//geo
.frame(height: 100)
TextField("Third", text: $thirdText)
Text("TextEditor top is \(offsetCGSize.height)")
}//v
.padding()
}//scroll
.onPreferenceChange(OffsetPreferenceKey.self) { value in
offsetCGSize = value
print("offsetCGSize is \(offsetCGSize)")
}
}//body
}//so view
private struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGSize = CGSize.zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}//off pref
Any guidance would be appreciated. Xcode 14.0.1, iOS 16
Like JohnSF, I have also been searching for use cases for preferenceKey. After a lot of searching and playing around, I wanted to post some information that hopefully will help others. Essentially there are 3 parts to a successful implementation. In my example, I needed to be able to react the any size change to a ScrollView (i.e. different screen resolutions, portrait vs landscape).
The first part is to define a preferenceKey struct with the value you want to track. In my example, I decided to get the CGRect of the ScrollView so I had the position (x, y) and size (width, height)
struct MyPreferenceKey: PreferenceKey {
typealias Value = CGRect // <-- 1.
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
So, if you wanted to track just the size, replace all the CGRect with CGSize, etc.
Next, define an #State variable to store the values you want to track. In my case I used
#State private var rect: CGRect: .zero
So my changes I'm tracking will be found in my "rect" variable anytime I want them.
The next part is to decide where to place your preference key. Part of this has to include a GeometryReader. In this example, I have a hierarchy of ScrollView -> GeometryReader -> ZStack. So, I placed the preferenceKey on the ZStack. Both the GeometryReader and the ZStack will by default fit the entire size of the ScrollView. I used the GeoProxy (I called this "scroll") to fill out my CGRect by defining the origin and size of a my CGRect
#State private var rect: CGRect = .zero
ScrollView(.horizontal, showsIndicators: false) {
GeometryReader { scroll in
ZStack {
// Stuff
} //: ZSTACK
.preference(key: MyPreferenceKey, value: CGRect(origin: CGPoint(x: scroll.size.width / 2, y: scroll.size.height / 2), size: scroll.size)) // <-- 2.
} //: GEOMETRY SCROLL
} //: SCROLL
.onPreferenceChange(MyPreferenceKey.self) { value in
print("PreferenceChange \(value)")
rect = value // <-- 3.
}
Then, as shown above, I placed the onPreferenceChange modifier on the actual ScrollView so that whenever the ScrollView changed, I would know its new size and position by using my "rect" variable.
Lastly if there is a View that you want to track the size of like an image, you can place your preference key and a GeometryReader in the background like this.
Image("someImage")
.background(
GeometryReader { geometry in
Color.clear
.preferenceKey here to track size
Then put your .onPreferenceChange on something like a VStack, etc. Then if you rotate your device, you can read the new size.
For others - I don't understand why, but by moving the two TextFields, the Image and the reporting Text outside of the ScrollView I get the results I expect. (In this file, I changed the key name to OffsetPreferenceKey2)
struct SOView2: View {
#State private var firstText: String = "first"
#State private var secondText: String = "second"
#State private var thirdText: String = "third"
#State private var offsetCGSize: CGSize = .zero
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
TextField("First", text: $firstText)
.padding(.horizontal)
ScrollView {
VStack {
ForEach(0..<20) {index in
Text("Line number \(index)")
}
GeometryReader { teGeo in
TextEditor(text: $secondText)
.frame(height: 100)
.overlay(
RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 2)
)
.overlay(
Text("\(teGeo.frame(in: .global).minY)")
)
.preference(key: OffsetPreferenceKey2.self, value: CGSize(width: 0, height: teGeo.frame(in: .global).minY))
}//geo
.frame(height: 100)
}//v
.padding()
}//scroll
.onPreferenceChange(OffsetPreferenceKey2.self) { value in
offsetCGSize = value
print("offsetCGSize is \(offsetCGSize)")
}// pref change
TextField("Third", text: $thirdText)
.padding(.horizontal)
Text("TextEditor top is \(offsetCGSize.height)")
}//outer v
}//body
}//so view
private struct OffsetPreferenceKey2: PreferenceKey {
static var defaultValue: CGSize = CGSize.zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}//off pref

How to fit horizontal ScrollView to content's height?

I have a horizontal scroll view with a LazyHStack. How do I size the scroll view to automatically fit the content inside?
By default, ScrollView takes up all the vertical space possible.
struct ContentView: View {
var numbers = 1...100
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(numbers, id: \.self) {
Text("\($0)")
.font(.largeTitle)
}
}
}
}
}
This code can be MUCH better
I have show it as BASE for your final solution
But it works
struct ContentView1111: View {
var numbers = 1...100
var body: some View {
VStack {
FittedScrollView(){
AnyView(
LazyHStack {
ForEach(numbers, id: \.self) {
Text("\($0)")
.font(.largeTitle)
}
}
)
}
// you can see it on screenshot - scrollView size
.background(Color.purple)
Spacer()
}
// you can see it on screenshot - other background
.background(Color.green)
}
}
struct HeightPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat = 40
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct FittedScrollView: View {
var content: () -> AnyView
#State private var contentHeight: CGFloat = 40
var body: some View {
VStack {
ScrollView(.horizontal) {
content()
.overlay(
GeometryReader { geo in
Color.clear
.preference(key: HeightPreferenceKey.self, value: geo.size.height)
})
}
.frame(height: contentHeight)
}
.onPreferenceChange(HeightPreferenceKey.self) {
contentHeight = $0
}
}
}

Align Views by constraints

I would like to build a view similar to this
✓ **Title Text**
Description Text
Where the icon an the title text have the same horizontal center ant the title text and the description text have the same alginment at the left.
Since i could not find any possiblity in SwiftUI to set constraints I am a little bit stuck.
The best solution i could come up was this
HStack(alignment: .top, spacing: Constants.Stacks.defaultHorizontalSpacing) {
challengeTask.status.getIconImage()
VStack(alignment: .leading, spacing: Constants.Stacks.defaultVerticalSpacing) {
Text(challengeTask.title)
.titleText()
Text(challengeTask.description)
.multilineTextAlignment(.leading)
.descriptionText()
Spacer()
}
}
But this does not align the icon horiztontally with the title text
iOS 16
struct ContentView: View {
var body: some View {
HStack(alignment: .firstTextBaseline) {
// ^^^^^^^^^^^^^^^^^
Image(systemName: "checkmark")
VStack {
Text("Title").font(.title.bold())
Text("Content").multilineTextAlignment(.leading)
}
}
}
}
Using hidden
struct ContentView: View {
var body: some View {
VStack {
HStack {
iconImage()
Text("Title").font(.title.bold())
}
HStack {
iconImage().hidden()
Text("Content").multilineTextAlignment(.leading)
}
}
}
private func iconImage() -> some View {
// Change it to your image
Image(systemName: "checkmark")
}
}
Using GeometryReader
struct ContentView: View {
#State private var iconSize: CGFloat = .zero
var body: some View {
VStack {
HStack(spacing: 10) {
Image(systemName: "checkmark")
.readSize { iconSize = $0.width }
Text("Title")
.font(.title.bold())
}
Text("Content")
.padding(.leading, iconSize + 10)
}
}
}
fileprivate struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { }
}
extension View {
func readSize(onChange: #escaping (CGSize) -> Void) -> some View {
modifier(ReadSize(onChange))
}
}
fileprivate struct ReadSize: ViewModifier {
let onChange: (CGSize) -> Void
init(_ onChange: #escaping (CGSize) -> Void) {
self.onChange = onChange
}
func body(content: Content) -> some View {
content.background(
GeometryReader { geometryProxy in
Color.clear.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { }
}
}
Hope this help!
I think the right way to deal with this is using a custom alignment as described here.
In your case you have 2 possibilities depending on how you want to implement the UI hierarchy:
Align image center with title center
Align description leading with title leading
Playground for both versions:
import SwiftUI
import PlaygroundSupport
extension HorizontalAlignment {
private struct TitleAndDescriptionAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[HorizontalAlignment.center]
}
}
static let titleAndDescriptionAlignmentGuide = HorizontalAlignment(
TitleAndDescriptionAlignment.self
)
}
extension VerticalAlignment {
private struct ImageAndTitleCenterAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[VerticalAlignment.center]
}
}
static let imageAndTitleCenterAlignmentGuide = VerticalAlignment(
ImageAndTitleCenterAlignment.self
)
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
VStack(alignment: .titleAndDescriptionAlignmentGuide) {
HStack {
Image(systemName: "rosette")
Text("Title text")
.font(.largeTitle)
.alignmentGuide(.titleAndDescriptionAlignmentGuide) { context in
context[.leading]
}
}
Text("Description text")
.alignmentGuide(.titleAndDescriptionAlignmentGuide) { context in
context[.leading]
}
}
HStack(alignment: .imageAndTitleCenterAlignmentGuide) {
Image(systemName: "rosette")
.alignmentGuide(.imageAndTitleCenterAlignmentGuide) { context in
context[VerticalAlignment.center]
}
VStack(alignment: .leading) {
Text("Title text")
.font(.largeTitle)
.alignmentGuide(.imageAndTitleCenterAlignmentGuide) { context in
context[VerticalAlignment.center]
}
Text("Description text")
}
}
}
}
}
}
PlaygroundPage.current.setLiveView(ContentView())

SwiftUI TabView with PageTabViewStyle dynamic height based on Content

How do I make my SwiftUI TabView with a PageTabViewStyle adjust its height to the height of the content?
I have a SwiftUI view like follows:
struct TabViewDynamicHeight: View {
var body: some View {
VStack {
TabView {
ForEach(0..<5, id: \.self) { index in
VStack {
Text("Text \(index)")
Text("Text \(index)")
Text("Text \(index)")
}
}
}
.tabViewStyle(PageTabViewStyle())
.background(Color.red)
.fixedSize(horizontal: false, vertical: true)
}
.background(Color.blue)
}
}
This produces an output like this:
You can see, that the content of the TabView is cut off. I'm aware, that I can remove .fixedSize, but than the view looks like this:
I would like the TabView to respond to the height of the content. Any ideas on how to achieve that?
A possible approach is to fetch content rect dynamically in run-time and transfer to parent via view prefs, so parent can set it as frame to fit content.
Tested with Xcode 13.3 / iOS 15.4
Here is main part:
VStack {
Text("Text \(index)")
Text("Text \(index)")
Text("Text \(index)")
}
.frame(maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewRectKey.self,
value: [$0.frame(in: .local)])
})
// ...
.frame(height: rect.size.height
+ 60 /* just to avoid page indicator overlap */)
.onPreferenceChange(ViewRectKey.self) { rects in
self.rect = rects.first ?? .zero
}
Complete test code in project is here
I took inspiration from Asperi' answer, however I had to modify some things to make it work in my case.
On the 'Content' of the TabView, I added these:
.overlay(GeometryReader { proxy in
Color.clear.preference(key: ViewRectKey.self, value: proxy.size)
})
.onPreferenceChange(ViewRectKey.self) { size in
if self.viewPagerSize.height == .zero {
self.viewPagerSize = size
}
}
Where viewPagerSize is initially:
#State var viewPagerSize: CGSize = .zero
My PreferenceKey looks like this:
struct ViewRectKey: PreferenceKey {
static let defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
In total the code looks like this:
#ViewBuilder
func createViewPager() -> some View {
TabView {
ForEach(views(), id: \.id) { _ in
createInformationSquareView()
.background(Color(.systemGray6))
.overlay(GeometryReader { proxy in
Color.clear.preference(key: ViewRectKey.self, value: proxy.size)
})
.onPreferenceChange(ViewRectKey.self) { size in
if self.viewPagerSize.height == .zero {
self.viewPagerSize = size
}
}
}
}
.cornerRadius(10)
.frame(height: self.viewPagerSize.height + 60)
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
.padding()
}

SwiftUI TextEditor Initial Content Size Is Wrong

iOS 14.4 + Xcode 12.4
I want to make a simple checklist in SwiftUI on iOS where the text for each item is a TextEditor.
First, I create the basic app structure and populate it with some demo content:
import SwiftUI
#main
struct TestApp: App {
#State var alpha = "Alpha"
#State var bravo = "Bravo is a really long one that should wrap to multiple lines."
#State var charlie = "Charlie"
init(){
//Remove the default background of the TextEditor/UITextView
UITextView.appearance().backgroundColor = .clear
}
var body: some Scene {
WindowGroup {
ScrollView{
VStack(spacing: 7){
TaskView(text: $alpha)
TaskView(text: $bravo)
TaskView(text: $charlie)
}
.padding(20)
}
.background(Color.gray)
}
}
}
Then each TaskView represents a task (the white box) in the list:
struct TaskView:View{
#Binding var text:String
var body: some View{
HStack(alignment:.top, spacing:8){
Button(action: {
print("Test")
}){
Circle()
.strokeBorder(Color.gray,lineWidth: 1)
.background(Circle().foregroundColor(Color.white))
.frame(width:22, height: 22)
}
.buttonStyle(PlainButtonStyle())
FieldView(name: $text)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
.background(Color.white)
.cornerRadius(5)
}
}
Then finally, each of the TextEditors is in a FieldView like this:
struct FieldView: View{
#Binding var name: String
var body: some View{
ZStack{
Text(name)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
.opacity(0)
TextEditor(text: $name)
.fixedSize(horizontal: false, vertical: true)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
}
}
}
As you can see in the screenshot above, the initial height of the TextEditor doesn't automatically size to fit the text. But as soon as I type in it, it resizes appropriately. Here's a video that shows that:
How can I get the view to have the correct initial height? Before I type in it, the TextEditor scrolls vertically so it seems to have the wrong intrinsic content size.
Note: views are left semi-transparent with borders so you can see/debug what's going on.
struct FieldView: View{
#Binding var name: String
#State private var textEditorHeight : CGFloat = 100
var body: some View{
ZStack(alignment: .topLeading) {
Text(name)
.background(GeometryReader {
Color.clear
.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
//.opacity(0)
.border(Color.pink)
.foregroundColor(Color.red)
TextEditor(text: $name)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -7))
.frame(height: textEditorHeight + 12)
.border(Color.green)
.opacity(0.4)
}
.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
print("Reporting height: \(value)")
}
}
First, I used a PreferenceKey to pass the height from the "invisible" text view back up the view hierarchy. Then, I set the height of the TextEditor frame with that value.
Note that the view is now aligned to topLeading -- in your initial example, the invisible text was center aligned.
One thing I'm not crazy about is the use of the edge insets -- these feel like magic numbers (well, they are...) and I'd rather have a solution without them that still kept the Text and TextEditor completely aligned. But, this works for now.
Update, using UIViewRepresentable with UITextView
This seems to work and avoid the scrolling problems:
struct TaskView:View{
#Binding var text:String
#State private var textHeight : CGFloat = 40
var body: some View{
HStack(alignment:.top, spacing:8){
Button(action: {
print("Test")
}){
Circle()
.strokeBorder(Color.gray,lineWidth: 1)
.background(Circle().foregroundColor(Color.white))
.frame(width:22, height: 22)
}
.buttonStyle(PlainButtonStyle())
FieldView(text: $text, heightToTransmit: $textHeight)
.frame(height: textHeight)
.border(Color.red)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
.background(Color.white)
.cornerRadius(5)
}
}
struct FieldView : UIViewRepresentable {
#Binding var text : String
#Binding var heightToTransmit: CGFloat
func makeUIView(context: Context) -> UIView {
let view = UIView()
let textView = UITextView(frame: .zero, textContainer: nil)
textView.delegate = context.coordinator
textView.backgroundColor = .yellow // visual debugging
textView.isScrollEnabled = false // causes expanding height
context.coordinator.textView = textView
textView.text = text
view.addSubview(textView)
// Auto Layout
textView.translatesAutoresizingMaskIntoConstraints = false
let safeArea = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
])
return view
}
func updateUIView(_ view: UIView, context: Context) {
context.coordinator.heightBinding = $heightToTransmit
context.coordinator.textBinding = $text
DispatchQueue.main.async {
context.coordinator.runSizing()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator : NSObject, UITextViewDelegate {
var textBinding : Binding<String>?
var heightBinding : Binding<CGFloat>?
var textView : UITextView?
func runSizing() {
guard let textView = textView else { return }
textView.sizeToFit()
self.textBinding?.wrappedValue = textView.text
self.heightBinding?.wrappedValue = textView.frame.size.height
}
func textViewDidChange(_ textView: UITextView) {
runSizing()
}
}
}

Resources