SwiftUI - TabView automatically changes selection when size changes - ios

I have a paging style TabView in a HStack with another view. I'm adding a selection binding to the TabView to programmatically control selection.
When a tab is tapped the second view in the HStack disappears and the TabView resizes to fill the space.
However, if this is done after scrolling through my TabView's contents what happens is the selection changes many times as the size changes, causing the TabView to erratically scroll through many pages, rather than stay on the same selection and resize.
To reproduce:
Create a new project
Replace ContentView with the following code
Scroll through ~20 pages
Tap on the rectangle (blue or red)
The selection will change by itself.
Tested on iOS 14.5
struct ContentView: View {
#State var showSidebar = true
#State var selection = 0
var body: some View {
HStack {
Rectangle()
.frame(width: showSidebar ? 100 : 0)
.frame(maxHeight: .infinity)
TabView(selection: $selection) {
ForEach(0..<100, id: \.self) { i in
Rectangle()
.foregroundColor(i % 2 == 0 ? .red : .blue)
.padding()
.onTapGesture {
withAnimation {
showSidebar.toggle()
}
}
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
}

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

How to have the frame of a scroll view change as you scroll in SwiftUI

I'm trying to setup an interface like this with a content view on top and a scroll view at the bottom. When you scroll up in scroll view, I want the scroll view's frame to move up until it's fullscreen. Similarly, once the scroll view is fullscreen I want it to come back down when you scroll in the reverse direction. This is similar to the modal sheet behavior with detents, but I want both views to be interactive at the same time. Is this possible?
Here's an example:
struct ContentView: View {
let content = 1...20
var body: some View {
VStack {
VStack {
Spacer()
HStack {
Spacer()
Text("Content View")
Spacer()
}
Spacer()
}
.background(Color(uiColor: .systemGray5))
List {
ForEach(content, id: \.self) { item in
Text("\(item)")
}
}
.frame(height: 300)
.listStyle(.plain)
}
}
}
I want the frame to change as you scroll up.

SwiftUI dragging ScrollView flickers as the keyboard is dismissing

When I develop the feature that allows users drag ScrollView to dismiss keyboard in SwiftUI, I find that if you drag ScrollView as the keyboard is dismissing, the ScrollView will flicker. That will destroy the experience of the feature.
Here's the video and minimal code example:
📺 Video
struct ContentView: View {
#State var text:String = ""
var body: some View {
ScrollView {
Rectangle()
.frame(height: 300)
TextField("test", text: $text)
.padding()
.background(Color.gray)
.padding()
}
}
}
It's caused because the keyboard forces the view to resize.
Add
.ignoresSafeArea(.keyboard, edges: .bottom)
to the bottom of your view to solve this issue

Adding a ZStack around a TabView/NavigationView moves content up into safe area when .blur is used

My application uses a TabView and contained in it there is a NavigationView so I can transition to different views on the selected tabs. I am using .navigationTitle together with .navigationBarTitleDisplayMode(.large) to show users where they are (in the example below the title is set to "Pfizer"). All is working fine as you can see below:
var body: some View {
TabView(selection: $selectedTab) {
NavigationView {
...
However, as soon as I put a ZStack around the TabView (or use .overlay) the entire views including the navigation header move up into the safe area even though I have not specified an .ignoresSafeArea anywhere! Here is how it looks like when I am using ZStack around it:
var body: some View {
ZStack(alignment: .center) {
TabView(selection: $selectedTab) {
NavigationView {
...
Note that I intend to use ZStack to conditionally show a view on top of my TabView/NavigationView as follows:
#Binding var condition: Bool
var body: some View {
ZStack(alignment: .center) {
TabView(selection: $selectedTab) {
NavigationView {
...
}
}
.blur(radius: condition ? 3 : 0)
if condition {
VStack {
VStack(alignment: .center) {
Text("Some text")
.font(.title2).padding(.bottom)
}
.padding()
.background(Color(.secondarySystemBackground))
}
.padding(.horizontal, 40)
}
}
}
I managed to figure out by trial and error that the problem only occurs if I am using the .blur view modifier on the TabView that is inside the ZStack. If I remove the .blur view modifier, then the problem that I described does not occur and so the initial comment of Asperi makes sense.
I do not understand this behavior and I have no idea why using .blur messes up the safe area. Can anybody explain this behavior to me please?

SwiftUI: SearchBar moving out of bound when the List is too long

I've implemented a search bar in my app inside a custom header. Beneath the search bar I have added a false List with 100 rows that is intended to show search results.
The problem that I'm facing is:
when the list appears, the search bar moves out of bounds. When I add a top padding of 400px, the search bar comes back to bounds. Link to video 1
The next two are a bit out of topic.
When the keyboard is on screen, the last few rows of the list are not visible. How to fix it? Link to video 2
How to set a background color for a List? I haven't been able to figure that out. (listRowBackground modifier isn't working as suggested by an article I read.)
I'm using Xcode 12.0 beta 6.
let screen = UIScreen.main.bounds
struct SearchBarView: View {
#Binding var search: String
#Binding var searchSelected: Bool
var body: some View {
VStack {
CustomTextField(text: $search, isFirstResponder: true)
.modifier(SearchBarTextFieldStyle(search: $search))
if !search.isEmpty {
List(1..<100) { i in
Text("Hello \(i)")
}.frame(width: screen.width)
}
}
.frame(width: screen.width, height: !search.isEmpty ? screen.height : 40)
.background(Color("ThemeColor"))
}
}
when the list appears, the search bar moves out of bounds. When I add
a top padding of 400px, the search bar comes back to bounds.
The issue is that everything is placed in one VStack. So when search is not empty anymore the TextField shares the space provided to it with the List.
Place the TextField in a separate Stack like this:
struct ContentView: View {
#State var search: String = ""
var body: some View {
// Everything wrapped in one Stack
VStack() {
// Separate Stack for the TextField
HStack() {
TextField("Title", text: self.$search)
}.padding()
// One Stack for the content
VStack {
if !search.isEmpty {
List(1..<100) { i in
Text("Hello \(i)")
}.frame(width: UIScreen.main.bounds.width)
}
}.frame(width: UIScreen.main.bounds.width)
.background(Color.red)
Spacer() // So that the TextField is also on top when no content is displayed
}
}
}
When the keyboard is on screen, the last few rows of the list are not
visible. How to fix it?
Add a padding to the bottom of the list but I'd recommend implementing the solution of this: Move TextField up when the keyboard has appeared in SwiftUI
E.g. with padding:
import SwiftUI
struct ContentView: View {
#State var search: String = ""
var body: some View {
VStack() {
HStack() {
TextField("Title", text: self.$search)
}.padding()
VStack {
if !search.isEmpty {
List(1..<100) { i in
Text("Hello \(i)")
}.frame(width: UIScreen.main.bounds.width)
.padding(.bottom, 300) // here padding
}
}.frame(width: UIScreen.main.bounds.width)
Spacer()
}
}
}
How to set a background color for a List? I haven't been able to
figure that out. (listRowBackground modifier isn't working as
suggested by an article I read.)
This question has also already been answered here:
SwiftUI List color background

Resources