iOS 14 SwiftUI Keyboard lifts view automatically - swift

I am using TextField in my view and when it becomes the first responder, it lefts the view as shown in the below GIF.
Is there any way I can get rid of this behavior?
Here is my code
NavigationView(content: {
ZStack{
MyTabView(selectedIndex: self.$index)
.view(item: self.item1) {
NewView(title: "Hello1").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item2) {
NewView(title: "Hello2").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item3) {
NewView(title: "Hello3").navigationBarTitle("")
.navigationBarHidden(true)
}
}.navigationBarHidden(true)
.navigationBarTitle("")
}).ignoresSafeArea(.keyboard, edges: .bottom)
// New View
struct NewView:View {
#State var text:String = ""
var title:String
var body: some View {
VStack {
Spacer()
Text("Hello")
TextField(title, text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
.onAppear {
debugPrint("OnApper \(self.title)")
}
}
}

For .ignoresSafeArea to work you need to fill all the available area (eg. by using a Spacer).
The following will not work (no Spacers, just a TextField):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
However, it will work when you add Spacers (fill all the available space):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
If you don't want to use Spacers you can also use a GeometryReader:
struct ContentView: View {
#State var text: String = ""
var body: some View {
GeometryReader { _ in
...
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}

You should apply the modifier on the ZStack, NOT the NavigationView
NavigationView(content: {
ZStack{
,,,
}.navigationBarHidden(true)
.navigationBarTitle("")
.ignoresSafeArea(.keyboard, edges: .bottom) // <- This line moved up
})
Full working example:
struct ContentView: View {
#State var text = ""
var body: some View {
VStack{
Spacer()
Text("Hello, World")
TextField("Tap to test keyboard ignoring", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}

What eventually worked for me, combining answers posted here and considering also this question, is the following (Xcode 12.4, iOS 14.4):
GeometryReader { _ in
VStack {
Spacer()
TextField("Type something...", text: $value)
Spacer()
}.ignoresSafeArea(.keyboard, edges: .bottom)
}
Both spacers are there to center vertically the textfield.
Using only the GeometryReader or the ignoresSafeArea modifier didn't do the trick, but after putting them all together as shown above stopped eventually the view from moving up upon keyboard appearance.

That's what I figured out:
GeometryReader { _ in
ZStack {
//PUT CONTENT HERE
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
It seems to work for me. In this case you do not need to check iOS 14 availability.

Related

SwiftUI: using MatchedGeometryEffect for a scrollView?

I am trying to understand MatchGeometryEffect a little better. I am trying to put it on each cell of a list in a scrollView. I want it to be where if showAlternate is false, the main cell is show but if showAlternate is true , then the individual cell will show the else of the conditional which would be a larger cell ( not part of the code yet). I have the matchedGeometryEffect on the ForEach right now but how can I do each cell indvidually?
struct ContentView: View {
#Binding var countries: [Country]
#State var showAlternate = false;
#Namespace var namespace;
var body: some View {
NavigationView {
ScrollView {
ForEach(Array(countries.enumerated()), id: \.1.id) { (index,country) in
LazyVStack {
HStack {
NavigationLink(
destination: CountryView(country: country),
label: {
HStack {
Image(country.image)
.resizable()
.scaledToFit()
Text(country.display_name)
.foregroundColor(Color.black)
.padding(.leading)
Spacer()
}
.padding(.top, 12.0)
.frame(height: 80)
}
).buttonStyle(FlatLinkStyle())
}
.padding(.horizontal, 16.0)
.overlay(Divider(), alignment: .top)
}
}
.background(Rectangle().opacity(0.2).matchedGeometryEffect(id: "shape", in: namespace))
}
}
.font(Font.custom("Avenir", size: 22))
}
}

Multiline text truncated in ScrollView

I'm having a rather peculiar issue with multiline texts inside a ScrollView. I have a LazyVStack in the ScrollView and have SomeRow (see below) Views inside this stack.
I have a long multiline description inside this SomeRow View that SwiftUI always cuts off.
I have tried all of the solutions here but they either broke the layout, didn't work or straight up caused the app to crash.
How it looks at the moment:
I've tried to reduce the reproducing code down to a minimum, but it is unfortunately still quite long, because I want to preserve what look I am trying to accomplish.
Here is the SomeListView:
import SwiftUI
struct SomeListView: View {
var body: some View {
VStack{
Text("Title")
.font(.title)
ScrollView{
LazyVStack{
ForEach(0..<4){_ in
SomeRow(entries: EntryContainer(entries:
[
Entry("DESC\nLONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
))
Spacer().frame(height: 5)
Divider()
}
}
}.padding(16)
}
}
}
struct SomeListView_Previews: PreviewProvider {
static var previews: some View {
SomeListView()
}
}
SomeRow View
import SwiftUI
struct SomeRow: View {
var entries: [Entry]
var body: some View {
VStack(alignment: .leading, spacing: 0){
Text("title").frame(maxWidth: .infinity, alignment: .leading)
Spacer().frame(height: 5)
ForEach(entries){entry in
HStack{
VStack{
Text("00:00 - 00:00")
Spacer()
}.frame(minWidth: 110)
Divider()
VStack{
HStack{
Text("titleinner")
Spacer()
}
HStack{
Text(entry.description ?? "")
Spacer()
VStack{
Spacer()
Text("number")
}
}
Spacer()
}
Spacer()
}
}
}
}
}
struct SomeRow_Previews: PreviewProvider {
static var previews: some View {
SomeRow(entries:
[
Entry("DESC\nLONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
)
}
}
Entry Data Class
import Foundation
class Entry: Identifiable {
let description: String?
init(_ description: String? = nil) {
self.description = description
}
}
Any ideas for a workaround? I am assuming this is simply SwiftUI bug because if you set the same long description for both entries it magically shows both descriptions fully, which really doesn't make sense to me.
This is how it breaks when I use Text(entry.description ?? "").fixedSize(horizontal: false, vertical: true:
(The divider doesn't fill the full height anymore and alignment of the timestamps in the left column is wrong)
It’s nearly impossible to have single divider and maintain the same alignment as you are looking for. The closest I could get you is shown in below code. Hope it satisfies requirement to some extent.
import SwiftUI
struct SomeListView: View {
var obj = [
Entry("DESC LONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
var body: some View {
VStack{
Text("Title")
.font(.title)
ScrollView{
LazyVStack(){
ForEach(0..<4){_ in
SomeRow(entries: obj)
Spacer().frame(height: 5)
Divider()
}
}
}.padding(16)
}
}
}
class Entry: Identifiable {
let description: String?
init(_ description: String? = nil) {
self.description = description
}
}
struct SomeRow: View {
var entries: [Entry]
var body: some View {
VStack(alignment: .leading,spacing:7){
Text("title")
ForEach(entries){entry in
HStack(alignment: .top,spacing:15){
Text("00:00 - 00:00")
Divider()
VStack(alignment:.leading, spacing:8){
Text("titleinner")
HStack(alignment:.lastTextBaseline, spacing:0){
Text(entry.description ?? "")
Spacer()
Text("number")
}
}
}.fixedSize(horizontal: false, vertical: true)
}
}
}
}
struct SomeRow_Previews: PreviewProvider {
static var previews: some View {
SomeRow(entries:
[
Entry("DESC\nLONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
)
}
}
struct SomeListView_Previews: PreviewProvider {
static var previews: some View {
SomeListView()
}
}
Output-:
With the help of concepts from the answer from Tushar Sharma I was able to create a version that displays the multiline text and has a continuous divider between the two "columns".
The only changes are in SomeRow View (compared to my initial question):
struct SomeRow: View {
var entries: [Entry]
var body: some View {
VStack(alignment: .leading, spacing: 0){
Text("title").frame(maxWidth: .infinity, alignment: .leading)
Spacer().frame(height: 5)
ForEach(entries){entry in
HStack(alignment: .top){
VStack{
Text("00:00 - 00:00")
Spacer()
}.frame(minWidth: 110)
Divider()
VStack(alignment: .leading){
Text("titleinner")
HStack(alignment: .lastTextBaseline){
Text(entry.description ?? "")
Spacer()
Text("number")
}
Spacer()
}
Spacer()
}.fixedSize(horizontal: false, vertical: true)
}
}
}
}
Basically using specified alignments and using fixedSize on the whole container of the text works wonders.
This is how it looks like now:

SwiftUI Sheet is only working once when opened from navigation bar

I have the problem that my sheet is not working as it should. When using a second NavigationView inside the SecondView then the sheet works but it looks crazy (first screenshot). When I comment out the NavigationView in SecondView the app looks correct (second screenshot), but the sheet is only working once when I open it from the Add button in navigation bar. When I use the "open sheet"-button in SecondView then the sheet works correct.
Did I do something wrong with this:
.navigationBarItems(trailing: Button("Add") {
showSheet.toggle()
})
SecondView with NavigationView:
SecondView without NavigationView (how it should look like):
This is the code I'm using:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Start View")
.font(.title)
NavigationLink(destination: SecondView()) {
CustomButton()
}
}
}
}
}
struct CustomButton: View {
var body: some View {
HStack {
Spacer()
Text("Go To Second View")
.font(.headline)
.foregroundColor(.red)
.padding()
Spacer()
}
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.red, lineWidth: 2)
)
.padding()
}
}
struct SecondView: View {
#State private var showSheet = false
var body: some View {
//NavigationView{
Text("Second View")
Button("open sheet", action: {
showSheet.toggle()
})
.navigationBarBackButtonHidden(true)
.navigationBarItems(trailing: Button("Add") {
showSheet.toggle()
})
.sheet(isPresented: $showSheet, content: {
SecondViewsSheet()
})
//}
}
}
struct SecondViewsSheet: View {
var body: some View {
Text("Sheet")
.navigationBarItems(trailing: Button("Test"){
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I use Xcode 12.2 with an iPhone 12 Pro simulator.

SwiftUI: Can't get the transition of a DetailView to a ZStack in the MainView to work

I can't find the answer to this anywhere, hopefully one of you can help me out.
I have a MainView with some content. And with the press of a button I want to open a DetailView. I am using a ZStack to layer the DetailView on the top, filling the screen.
But with the following code I can't get it to work. The DetailView does not have a transition when it inserts and it stops at removal. I have tried with and without setting the zIndex manually, and a custom assymetricalTransition. Couldn't get that to work. Any solutions?
//MainView
#State var showDetail: Bool = false
var body: some View {
ZStack {
VStack {
Text("Hello MainWorld")
Button(action: {
withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Show detail")
}
}
if showDetail {
ContentDetail(showDetail: $showDetail)
.transition(.move(edge: .bottom))
}
}
.edgesIgnoringSafeArea(.all)
}
And here is the DetailView:
//DetailView
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: { withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
}
The result of this code is this:
I'm running Xcode 11.4.1 so implicit animations doesn't seem to work either. Really stuck here, hope one of you can help me out! Thanks :)
Here is a solution. Tested with Xcode 11.4 / iOS 13.4.
struct MainView: View {
#State var showDetail: Bool = false
var body: some View {
ZStack {
Color.clear // extend ZStack to all area
VStack {
Text("Hello MainWorld")
Button(action: {
self.showDetail.toggle()
}) {
Text("Show detail")
}
}
if showDetail {
DetailView(showDetail: $showDetail)
.transition(AnyTransition.move(edge: .bottom))
}
}
.animation(Animation.spring()) // one animation to transitions
.edgesIgnoringSafeArea(.all)
}
}
struct DetailView: View {
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: {
self.showDetail.toggle()
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // fill in container
.background(Color.yellow)
}
}

How to hide navigationBar in SwiftUI when parent Views are with titles

I'm trying to make a simple app using SwiftUI using NavigationView and the last View is a video player (which I obviously don't want to have a navigationBar). The thing is that every other View leading to the player has navigationBarTitle and it just stays.
What I have:
ContentView :
var body: some View {
NavigationView {
VStack {
Text("Sample")
DetailedView(data: CustomData.sample)
}
.navigationBarTitle(Text("Main"))
}
}
DetailedView:
#ObservedObject var data: CustomData
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack {
ForEach(data.array) { videoData in
NavigationLink(destination: VideoDetailed(videoData: videoData)) {
VideoRow(episode: episode)
}
}
}
}
}
VideoDetailed:
#ObservedObject var videoData: VideoData
var body: some View {
VStack {
NavigationLink(destination: PlayerContainerView(url: videoData.url)
.navigationBarBackButtonHidden(true)
.navigationBarTitle(Text("_"))
.navigationBarHidden(true)){
Image(systemName: "play.fill")
.resizable()
.foregroundColor(.white)
.aspectRatio(contentMode: .fit)
.shadow(radius: 5)
.frame(maxWidth: 50)
}
Text(videoData.description)
Spacer()
}
.navigationBarTitle(Text(videoData.title), displayMode: .inline)
}
As a result of this code I get no back button and a "_" for title with a navigation bar
You need to set the title to an empty string and the displayMode to inline for it to hide.
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(false)
Just remove this line:
.navigationBarTitle(Text("_"))
from VideoDetailed.