SwiftUI Dismiss keyboard on List NavigationLink item tap - ios

I have a list of items and a text field for search keywords. When I do search on the list and tap on items, navigationlink works faster than keyboard dismiss, and this causes a disabled area in the next scroll view.
// search TextField
HStack {
Spacer(minLength: 10)
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("Search",
text: self.$textbox){
UIApplication.shared.endEditing()
}
.onChange(of: textbox) {
print($0)
dictionaryListVM.getResult(language: self.currentLanguage, text: self.textbox)
self.theId += 1
}
.accessibility(identifier: "loginUserName")
.background(Color.white)
.frame(height: 10, alignment: .leading)
.padding()
}.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.gray, lineWidth: 2)
)
// Subview for displaying items:
GeometryReader { reader in
ScrollView {
VStack {
if model.dataLoaded {
List {
ForEach(self.model.englishDictionaries, id: \.id) { item in
ZStack {
VStack(alignment: .leading, spacing: 4) {
Text("\(item.value)").font(.title2)
Text("\(item.pairWord)").foregroundColor(.gray).font(.caption)
}
NavigationLink(destination: WordDetailView(englishWordId: item.id, englishWord: item.value, lang: "en")) {
}
.isDetailLink(false)
.buttonStyle(PlainButtonStyle()).frame(width:0).opacity(0)
}
}
}.frame(height: reader.size.height)
.animation(.linear)
}else{
Text("Words sync inprogress, please comeback later.")
}
}
}.padding(.leading, 10)
}
So, I want to make sure that my keyboard is dismayed before navigation to the next view.
check out demo for the issue in action

If you are using iOS 15
I believe you can make use of the #FocusState Property wrapper to dismiss the keyboard.
First you will have to define a variable to hold the focus value.
#FocusState private var isFocused: Bool
Adding the .focused(_:) View Modifier to the TextField:
TextField("Hello There", text: $someText)
.focused($isFocused)
Toggling $isFocused on button press or navigation link press. You can set it as shown below:
isFocused = false

Related

SwiftUI ScrollView extra padding when go to another screen with showed keyboard

The default "Keyboard Avoidance" SwiftUI is used.
First GIF
If you put the code in VStack and add another View to it, then the container rises
Second GIF
I don't want to delete Keyboard Avoidance. I need to remove extra spacing
scrollDismissesKeyboard for ScrollView is not an option
minimal iOS version is iOS 16
struct ContentView: View {
#State var text: String = "Bu bu?"
var body: some View {
NavigationStack {
ScrollViewReader { proxy in
VStack {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
Spacer()
.frame(height: 500)
TextField("", text: $text)
.padding(.bottom, 70)
.frame(height: 40)
.frame(maxWidth: .infinity)
.background(Color.red)
NavigationLink("Screen 2", destination: {
Text("SwiftUI - Nice to meet you, let's never meet again")
})
}
}
Text("I'm in VSTack after scroll view")
}
}
}
}
}
I looked it up with a hierarchy view, and noticed that a UIInputSetHostView is created with a height of 216
View hierarchy 1
View hierarchy 2
disableAutocorrection not working

SwiftUI expandable component Animation issue

I created a custom bottom bar with horizontal expandable tabs.
I have two animations: (1) tab expand/collapse animation, (2) tab bar translation animation (when some tab was expanded, it affects move other tabs)
struct AirTabView: View {
#Binding var isActive: Bool
var model: TabModel
var action: (() -> ())
var body: some View {
HStack(spacing: 10) {
Image(model.imageName)
.foregroundColor(.black)
if isActive {
Text(model.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.init(uiColor: .label)
)
.lineLimit(1)
}
}
.padding(10)
.background(isActive ? Color(.secondarySystemBackground) : .clear)
.cornerRadius(11)
.onTapGesture(perform: action)
.animation(.linear(duration: 2), value: isActive)
}
}
struct AirTabBar: View {
var tabs: [TabModel]
var actions: [TabActionModel]
#State private var selectedIndex = 0
var body: some View {
HStack(spacing: 10) {
ForEach(0..<tabs.count, id: \.self) { index in
AirTabView(isActive: .constant(selectedIndex == index), model: tabs[index]) {
selectedIndex = index
}
}
Spacer()
ForEach(0..<actions.count, id: \.self) { index in
AirTabActionView(model: actions[index])
}
}
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background()
.cornerRadius(16)
.shadow(
color: .init(uiColor: .black
.withAlphaComponent(0.07)
),
radius: 15,
x: 2)
.animation(.linear(duration: 2))
}
}
But sometimes, I have a visual bug when text that appears in an expanded cell overlaps the image at the animation start. I want that text always be and appear right side of the image.
Please explain to me what I did wrong. Sometimes RIGHT behavior happens, but I want to understand and fix WRONG
Expected effect is not clear, but observed behavior is due to transition (by default it is opacity), ie. when text is added conditionally it appears with opacity transition.
Here is a demo how it could be managed (so you can tune more if some other effect is needed).
Tested with Xcode 13.4 / iOS 15.5 (some missed things replaced)
Main part:
HStack(spacing: 10) {
if isActive {
HStack {
Image(systemName: model.imageName) // system name for testing !!
.foregroundColor(.black)
Text(model.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.init(uiColor: .label)
)
.lineLimit(1)
}
.transition(.identity) // content is same !!
} else {
Image(systemName: model.imageName)
.foregroundColor(.black)
.transition(.identity) // same is here !!
}
}
.animation(.none, value: isActive) // << don't animate !!
.padding(10)
so content of label is not animated and replaced Image <> Image Title, which gives title always appears right after image, and only highlighting box is animated.
Test module on GitHub

TextField In Tab View will show space upon keyboard

I'm using text field in Tab view, but when keyboard shows out. There has a space upon keyboard.
enter image description here
var body: some View {
TabView {
TestView()
}
}
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0..<100) { data in
Text("\(data)")
}
}
Spacer()
HStack {
Image(systemName: "paperplane")
TextField("test field", text: $test)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
.padding()
.ignoresSafeArea(.keyboard, edges: .bottom)
}
If I'm using without Tab view, the keyboard works totally fine.
enter image description here
I took some search and put .ignoresSafeArea(.keyboard, edges: .bottom), I don't know why it still doesn't work.
Modifier should be applied in correct place:
HStack {
Image(systemName: "paperplane")
TextField("test field", text: $test)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.ignoresSafeArea(.keyboard, edges: .bottom) // << here !!
Tested with Xcode 13.4 / iOS 15.5

SwiftUI change view from first screen to tabview screen

I want to change views once the user taps 'get started' but due to having navigation view in my first view, it is showing back button on my next screen which I don't want. Please see the images attached below.
Code for the first view is below:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
NavigationLink(destination: ViewChanger()) {
Text("Get Started")
.font(.headline)
.navigationBarBackButtonHidden(true)
}
}
.padding()
}
}
}
back button showing on screen 2
First view
Change the location of your navigationBackButtonHidden modifier so that it actually modifies the view that you're going to (and not the NavigationLink label):
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
NavigationLink(destination: ViewChanger()
.navigationBarBackButtonHidden(true) // <-- Here
) {
Text("Get Started")
.font(.headline)
}
}
.padding()
}
}
}
If you want not only the back button to be gone, but the entire header bar, you can use the .navigationBarHidden(true) modifier.
Also, if you run this on iPad at all, you probably want .navigationViewStyle(StackNavigationViewStyle()) added to the outside of your NavigationView
If you use a NavigationLink (in a NavigationView), the view will be pushed. If you want to replace the view, you can do this with an if statement.
For example, this could be implemented like this:
struct ContentView: View {
#State var showSecondView: Bool = false
var body: some View {
if !showSecondView {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
Button(action: { showSecondView = true }) {
Text("Get Started")
.font(.headline)
}
}
.padding()
} else {
TabView {
// ...
}
}
}
}

Present Modal fullscreem SwiftUI

how can i present a modal that will take up the fullscreen and can't be dismissed by swiping it down? Currently I am using .sheet on a view to present a modal that is dismissible.
I haven't noticed any beta changes in Xcode that changes this behavior.
Any help would be appreciated :)
SwiftUI 1.0
I'm not sure if this what you'd want to go with but it's possible to create your own modal screen by using the ZStack and a state variable to control the hiding/showing of it.
Code
struct CustomModalPopups: View {
#State private var showingModal = false
var body: some View {
ZStack {
VStack(spacing: 20) {
Text("Custom Popup").font(.largeTitle)
Text("Introduction").font(.title).foregroundColor(.gray)
Text("You can create your own modal popup with the use of a ZStack and a State variable.")
.frame(maxWidth: .infinity)
.padding().font(.title).layoutPriority(1)
.background(Color.orange).foregroundColor(Color.white)
Button(action: {
self.showingModal = true
}) {
Text("Show popup")
}
Spacer()
}
// The Custom Popup is on top of the screen
if $showingModal.wrappedValue {
// But it will not show unless this variable is true
ZStack {
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.vertical)
// This VStack is the popup
VStack(spacing: 20) {
Text("Popup")
.bold().padding()
.frame(maxWidth: .infinity)
.background(Color.orange)
.foregroundColor(Color.white)
Spacer()
Button(action: {
self.showingModal = false
}) {
Text("Close")
}.padding()
}
.frame(width: 300, height: 200)
.background(Color.white)
.cornerRadius(20).shadow(radius: 20)
}
}
}
}
}
Example
(Excerpt from "SwiftUI Views" book)
So here, your popup is small, but you can adjust the dimensions to make it fullscreen with the frame modifier that is on that VStack.

Resources