How to prevent TextEditor from scrolling in SwiftUI? - swift

I am trying to create a list of TextEditors as part of a SwiftUI app. I want the individual editors not to scroll, as they make up a larger scrolling list. However, when I add the TextEditors to the list, they compress down and each view becomes individually scrollable. Is there a modifier / trick to getting the TextEditor to always fit its text content without scrolling, so I can achieve this?
A minimal example of what I'm trying to do is below:
struct ParentView: View {
var items: [Item]
var body: some View {
List(items, id: \.id) { item in
EditorView(item: item)
}
}
}
struct EditorView: View {
#ObservedObject var item: Item
var body: some View {
TextEditor(text: $item.text)
}
}
This is in a macOS SwiftUI app, not an iOS one, in case there is any platform differences.
Edit: As pointed out in the comments I tried this approach Dynamic row hight containing TextEditor inside a List in SwiftUI but it didn't seem to work correctly - the rows still didn't expand properly.

To solve this problem, I ended up using the .fixedSize(horizontal: false, vertical: true) modifier to fix the TextEditor to its full size, which then allowed the solution in Dynamic row hight containing TextEditor inside a List in SwiftUI to work as expected within the list.

iOS 15
can use in init UITextView.appearance().isScrollEnabled = false
init(para: EditorParagraph) {
self._para = StateObject(wrappedValue: para)
UITextView.appearance().backgroundColor = .clear
UITextView.appearance().textDragInteraction?.isEnabled = false
UITextView.appearance().isScrollEnabled = false // here
}
var body: some View{
ZStack {
Text(para.content.isEmpty ? para.placeholder : para.content)
.font(.system(size: para.fontSize, weight: para.content.isEmpty ? .thin : .regular))
.lineSpacing(6)
.opacity(para.content.isEmpty ? 1 : 0)
.padding(.all, 8)
.frame(maxWidth: .infinity, alignment: .leading)
TextEditor(text: $para.content)
.font(.system(size: para.fontSize, weight: .regular))
.foregroundColor(.black)
.lineSpacing(6)
}
}

Will be fixed in macOS 13.0 with a scrollDisabled(_disabled: Bool) function. Seems to be a major hole in TextEditor
https://developer.apple.com/documentation/swiftui/list/scrolldisabled(_:)

Related

Is there a way to solve the no-data when I click on the view for the first time? In Swift

I want to display the content of a Card with a Modal View that appears when I click on the Custom Card, but when I click only one card (not different card) , it displays a Model View with no data in it. After that, it works good for every card.
This is the code where the error is:
import SwiftUI
struct GoalsView: View {
#Binding var rootActive: Bool
#State private var showSheet = false
#Binding var addGoalIsClicked: Bool
#State private var selectedGoal : Goal = Goal(title: "", description: "")
var body: some View {
ScrollView {
VStack(){
HStack{
Text("Add goals")
.fontWeight(.bold)
.font(.system(size: 28))
Spacer()
}
.padding()
ForEach(goalDB) {goal in
Button() {
selectedGoal = goal
showSheet = true
}label: {
Label(goal.title, systemImage: "")
.frame(maxWidth: .infinity)
.foregroundColor(.black)
}
.padding()
.fullScreenCover(isPresented: $showSheet) {
ModalView(rootActive: $rootActive, goal: selectedGoal)
}
.background {
RoundedRectangle(cornerRadius: 10)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
}
}
.padding(4)
}
}.background(Color.gray.opacity(0.15))
}
}
I tried to find a solution on the Internet (someone has my same problem), reading the documentation of the FullScreenCover but I can not find out how to do it
Instead of using a Boolean to control whether or not to show a sheet and/or full screen cover (I'm going to use "sheet" throughout, the principle is identical for both), you can instead use an optional object.
In this scenario, you start with an optional value that is nil. When you want your sheet to display, you set the object to an actual value. Dismissing the sheet will return your state object to nil, just as dismissing a sheet managed by a Boolean resets the state to false.
In your code, you can remove your showSheet state value, and convert selectedGoal to an optional:
#State private var selectedGoal: Goal?
Your button action now only needs to set this value:
Button {
selectedGoal = goal
} label: {
// ...
}
And then in your modifier, you use the item: form instead. The closure receives the unwrapped version of the state object – i.e., it'll always get a Goal, rather than a Goal?. This is the value you should pass into the view being presented:
.fullScreenCover(item: $selectedGoal) { goal in
ModalView(rootActive: $rootActive, goal: goal)
}

iOS16 Bug Keyboard breaks layout on sheet dismissal SwiftUI

In iOS16 faced a bug with keyboard inside sheet, when sheet is dismissing keyboard disappears(what is ok), but layout is not updated. I saw only 1 question on same problem and wondering maybe somebody found a temporary workaround until Apple don't fix this.
Code to reproduce :
struct Test: View {
#State var isPresented: Bool = false
#State var text: String = ""
var body: some View {
VStack{
Button {
isPresented.toggle()
} label: {
Text("PRESENT")
}
}
.sheet(isPresented: $isPresented) {
ZStack {
Color.red
VStack{
TextField("Test", text: $text)
.frame(height: 50, alignment: .center)
Spacer()
Rectangle()
.fill(Color.blue)
.frame(width:300, height: 50)
}
}
}
}
}
Video:
https://vimeo.com/758845068
The .ignoresSafeArea() fixes the issue, but...
This will have as result of keyboard overlap in your UI and not be able to scroll to see all your elements.
I use the .adaptsToKeyboard() custom modifier taken from this answer
and then using it where needed with this particular order.
VStack {...}
.adaptsToKeyboard()
.ignoresSafeArea()

UI does not refresh when TextEditor changed

I have the following code. When text changed, the overlaid content does not removed?
ZStack {
TextEditor(text: $text)
if text.isEmpty {
Text("Content")
}
}
Initially the overlaid content is shown, but if I continue to type on TextEditor, the content remains on screen. I have also check text.count == 0 and the result is the same.
Here's the screenshot - the $text in TextEditor (yellow background) is obviously not empty. (Tested on iPadOS/iOS 14.6 and 14.7 beta)
updated answer showing that as the text becomes empty (or not) the view changes:
struct ContentView: View {
#State private var text: String = ""
var body: some View {
VStack {
TextEditor(text: $text)
.overlay(text.isEmpty ? Text("Content isEmpty") : Text(""))
.frame(width:222, height: 111).border(Color.red) // <-- testing
text.isEmpty ? Text("text is empty") : Text("text NOT empty")
}
}
}
I found the issue (and the solution):
I have a GeometryReader at the beginning of the body, once I removed it, this issue gone away and everything works as expected.
This might be a SwiftUI bug.

SwiftUI TextField takes max width

I have a navigation list with each list item being in this format:
HStack {
TextField("Insert something here.",text: self.$userData.pages[i].title)
.border(Color.blue)
Spacer()
}
This results in the following view:
The touchable area is highlighted by the blue border and it takes the whole width of the row
The problem with this is that despite the list item being a navigation link, the user clicking anywhere along the item will result in them editing the text content. What I would prefer is a TextField that has the same width as a Text:
The blue border wraps the text instead of taking the max width
So if the user clicks outside the TextField, the navigation works, but if they click on the text, it will let them edit the text. (The above view is with Text field).
Apologies if I've asked an unclear or bad question. I'm new to Stack Overflow and SwiftUI.
Edit:
I've tried using the fixedSize modifier, and the TextField correctly wraps my Text, but now the Navigation Link doesn't work (i.e. clicking on it just doesn't navigate). This is my full code:
NavigationLink(destination: PageView(page: self.userData.pages[i])) {
HStack {
Button(action: {}){
TextField(" ", text: self.$userData.pages[i].title)
.fixedSize()
}
.buttonStyle(MyButtonStyle())
.border(Color.blue)
Spacer()
}
}
No need to apologize, your question is clear.
You can do this by using fixedSize()
so your code should be like this
HStack {
TextField("Insert something here.",text: self.$userData.pages[i].title)
.border(Color.blue)
.fixedSize()
Spacer()
}
You can further specify how would you like the stretch to be, either vertical or horizontal or even both by passing parameters like so
.fixedSize(horizontal: true, vertical: false)
UPDATED ANSWER TO MATCH YOUR NEW REQUIREMENTS
import SwiftUI
struct StackOverflow5: View {
#State var text: String = ""
#State var selection: Int? = nil
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: Page2(), tag: 1, selection:self.$selection) {
Color.clear
.onTapGesture {
self.selection = 1
}
}
TextField("Text", text: self.$text)
.fixedSize()
}
}
}
}
struct StackOverflow5_Previews: PreviewProvider {
static var previews: some View {
StackOverflow5()
}
}
struct Page2: View {
var body: some View {
Text("Page2")
}
}
We used a ZStack here to separate between our TextField and our NavigationLink so they can be interacted with separately.
Note the use of Color.clear before our TextField and this is on purpose so that our TextField has interaction priority. Also we used Color.clear because it will stretch as a background and it's clear so it's not visible.
Obviously I hard coded 1 here but this can be from List or a ForEach
Additionally, if you don't want to use selection and tag you can do something like this
...
#State var isActive: Bool = false
...
NavigationLink(destination: Page2(), isActive: self.$isActive) {
Color.clear
.onTapGesture {
self.isActive.toggle()
}
}
....

Dynamic row hight containing TextEditor inside a List in SwiftUI

I have a List containing a TextEditor
struct ContentView: View {
#State var text: String = "test"
var body: some View {
List((1...10), id: \.self) { _ in
TextEditor(text: $text)
}
}
}
But it's items are not growing on height change of the TextEditor. I have tried .fixedSize() modifier with no luck. What am I missing here?
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
As far as I can see from view hierarchy TextEditor is just simple wrapper around UITextView and does not have more to add, so you can huck into that layer and find UIKit solution for what you need, or ...
here is a demo of possible approach to handle it at SwiftUI level (the idea is to use Text view as a reference for wrapping behaviour and adjust TextEditor exactly to it)
Tested with Xcode 12b / iOS 14 (red border is added for better visibility)
Modified your view:
struct ContentView: View {
#State var text: String = "test"
#State private var height: CGFloat = .zero
var body: some View {
List {
ForEach((1...10), id: \.self) { _ in
ZStack(alignment: .leading) {
Text(text).foregroundColor(.clear).padding(6)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
})
TextEditor(text: $text)
.frame(minHeight: height)
//.border(Color.red) // << for testing
}
.onPreferenceChange(ViewHeightKey.self) { height = $0 }
}
}
}
}
Note: ViewHeightKey is a preference key, used in my other solutions, so can be get from there
ForEach and GeometryReader: variable height for children?
How to make a SwiftUI List scroll automatically?
Automatically adjustable view height based on text height in SwiftUI