SwiftUI TextField takes max width - swift

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()
}
}
....

Related

Make all View contained TextField be visible above keyboard

It is not question about how to keep TextField above keyboard (put it inside ScrollView), I wonder how to keep TextField containing View fully be visible above Keyboard. For example behind TextField some info text, which should be visible while user print the text.
VStack { // This all should be above keyboard
TextField(...)
Text("Some hints about entered text")
}
In screens:
Now I have this (you can see TextField just above keyboard, but we don't see content under TextField)
But I want to get this (With "Some additional info" label, which should also pop above keyboard when TextField become first responder):
I was try you code and all warks fine right now but you can try like this:
VStack(alignment: .leading) {
TextField(...)
Text("Some hints about entered text")
}
I don't know what I want because there's not much information I can get from the question, but is this right? I understand that you want to include a view that can be a hint from the view behind the TextField.
import SwiftUI
struct ContentView: View {
#State private var text = ""
var body: some View {
VStack { // This all should be above keyboard
Spacer()
TextField("", text: $text)
.background {
Text("Some hints about entered text")
.foregroundColor(.gray)
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Add Padding to TabView Page Indicator

I have a view that is a TabView with the style PageTabViewStyle(indexDisplayMode: .always which looks great for my use case however the page indicator is bumping right up to the safe area near the bottom of the screen and it looks bad. I'd like to move the page indicator up to some n value. here's a sample of my view to reproducing it. If this view were built on any device without a Home Button, it will ride on top of the home indicator line.
var body: some View {
ZStack {
TabView(selection: $homeVM.selectedPageIndex) {
// Any number of views here.
}
.frame(width: UIScreen.main.bounds.width)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .never))
}.edgesIgnoringSafeArea(.all)
}
I attempted to add the padding to the ZStack, which does work but then my TabView is cut off on the bottom, which means my cells disappear prematurely.
Here's an image for what I'm trying to fix. Notice the page indicator sits on the home bar indicator. I need the indicators pushed up, without pushing up the background ScrollView
Update #1
This view is being presented by a base view that I use to handle my navigation stack. The view is as follows. The important thing to note here is the .ignoresSafeArea() that I have on this view. I did that because it's a containing view for my eventual TabView to be presented from. Interestingly if I remove this modifier the indicators move up to a more manageable position, but then my form becomes clipped at the top and bottom of the device when scrolling, and that's not ideal.
struct BaseLaunchView: View {
#StateObject var baseNavVM = BaseLaunchViewModel()
#State var shouldLogin = false
#State var shouldRegister = false
var body: some View {
VStack {
switch baseNavVM.loggedIn {
case true:
HomeView()
default:
NavigationView {
VStack{
Spacer()
VStack(spacing: 30) {
VStack {
Text("Stello")
.font(Fonts.title)
Text("Life Groups")
.font(Fonts.body)
}
StelloDivider()
Text("Connect with like minded people, to fellowship and find your home.")
.font(Fonts.subheading)
.multilineTextAlignment(.center)
}
Spacer()
NavigationLink(destination: RegisterOptionsView(isLoggingIn: true), isActive: $shouldLogin) {
Button(action: {
shouldLogin.toggle()
}, label: {
Text("Login")
.font(Fonts.button)
}).buttonStyle(StelloFillButtonStyle())
}
NavigationLink(destination: RegisterOptionsView(isLoggingIn: false), isActive: $shouldRegister) {
Button(action: {
shouldRegister.toggle()
}, label: {
Text("Register")
.font(Fonts.button)
}).buttonStyle(StelloHollowButtonStyle())
}
}
}.accentColor(.black)
}
}
.padding()
.environmentObject(baseNavVM)
.ignoresSafeArea()
}
}

SwiftUI limit tappable area of picker in form

I would like to limit the tappable area of the picker in a form to only it's visible area of text (i.e. black rectangle in the picture below). Currently whole row can be tappable.
Screen capture of the program
Here is the code:
import SwiftUI
struct ContentView: View {
#State var rate: String = ""
#State var units = ["mL/day","mL/hour"]
#State var unit: Int = 0
var body: some View {
NavigationView{
Form{
HStack{
Text("Rate")
TextField("0", text: $rate)
Picker(selection: $unit, label: Text(""), content: {
ForEach(0..<units.count, content: { unit in
Text(units[unit])
})
})
.frame(width: 100.0)
.compositingGroup()
.clipped()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I read through this question and try to add .clipped() and .compositingGroup() but seems not useful.
You are going to have to redesign the UI a bit. I would recommend this:
struct ContentView: View {
#State var rate: String = ""
#State var units = ["mL/day","mL/hour"]
#State var unit: Int = 0
var body: some View {
NavigationView{
Form{
Section(header: Text("Rate")) { // Do This
HStack{
TextField("0", text: $rate)
.background(Color.red)
Picker(selection: $unit, label: Text(""), content: {
ForEach(0..<units.count, content: { unit in
Text(units[unit])
})
})
.frame(width: 100)
.clipped()
.background(Color.green)
}
}
}
}
}
}
I made colored backgrounds for each of the subviews. The issue is not that the whole row is selectable. It is that when you have a Picker() in the row, any view that is not itself selectable, in this case the Text() triggers the Picker(). If you use your code and put a colored background behind the text, you will see what happens. If you tap on the Picker() it triggers. If you tap on the TextField() you get an insertion point there. But if you tap on the Text() it triggers the Picker(). The .compositingGroup doesn't affect this behavior at all.
I have this exact design in an app, and the solution is to put the text into Section with a header, or put the Text() in the row above.

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

BackButton without text SwiftUI

Actually, this is the question.
I have a screen like this, I would like to change the BackButton.
My View screen:
enter image description here
I want instead of blue backbutton with standart array + text BACK, just to be black array WITHOUT text.
In other words, essentially remove the text from the backbutton and change the color to black. How not to achieve this?
You can use the appropriate modifiers inside the View you wish to be black and without text. You need to specify no button and supply your own image as well as the return functionality.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Hello, World!")
}
}
}
}
struct SecondView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Hello")
Text("Second view")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
self.presentation.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.left")
}
.foregroundColor(.black))
}
}
Edited: Inserted the appropriate image.