Any Modifier available to stop bounce of ScrollView in swiftUI ?
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
I tried below code but it not work for me. looks like it deprecated
ScrollView(alwaysBounceVertical: true) {
Image("test")
Text("Hello Text")
...
...
}
try using this line of code:
UIScrollView.appearance().bounces = false
You can use it like this:-
struct RoomDetailsView: View {
init() {
UIScrollView.appearance().bounces = false
}
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
Or you can write this line in AppDelegate to apply this behaviour throughout into your app.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIScrollView.appearance().bounces = false
}
You may use SwiftUI-Introspect library:
ScrollView {
// some content
}
.introspectScrollView { scrollView in
scrollView.alwaysBounceVertical = false
}
Set the ScrollView's showsIndicators parameter equal to false so the user interacting with your view doesn't activate the scroll indicator (it can happen even without scrolling enabled).
ScrollView(showsIndicators: false)
In the onAppear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = false
In the onDisappear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = true
If you set UIScrollView.appearance().bounces equal to false in the init, it will prevent all of your app's ScrollViews from bouncing. By setting it equal to true in the onAppear modifier and equal to false in the onDisappear modifier, you ensure that it only effects the one ScrollView.
Your ScrollView should look something like this.
ScrollView(showsIndicators: false) {
...
...
...
}
.onAppear {
UIScrollView.appearance().bounces = false
}
.onDisappear {
UIScrollView.appearance().bounces = true
}
A better solution would be to use viewbuilder and create your own scrollview that doesn't bounce when the content size is less than scrollview frame size.
import SwiftUI
struct BounceVerticalDisableScrollView<Content: View>: View {
#State private var alwaysBounceVertical = false
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
GeometryReader { scrlViewGeometry in
ScrollView {
content
.background(
GeometryReader {
// calculate height by clear background
Color.clear.preference(key: SizePreferenceKey.self,
value: $0.frame(in: .local).size.height)
}.onPreferenceChange(SizePreferenceKey.self) {
self.alwaysBounceVertical = $0 < scrlViewGeometry.size.height
}
)
}
// disable scroll when content size is less than frame of scrollview
.disabled(self.alwaysBounceVertical)
}
}
}
// return size
public struct SizePreferenceKey: PreferenceKey {
public static var defaultValue: CGFloat = .zero
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
So, that you can use the scrollview as:
var body: some View {
VStack (alignment: .leading, spacing: 10) {
BounceVerticalDisableScrollView{
VStack (alignment: .leading, spacing: 10) {
....
....
}.padding()
}
}
}
Apple introduced an new modifier named scrollBounceBehavior with iOS 16.4 that can be used to prevent the ScrollView from bouncing when the content is smaller than the screen.
https://developer.apple.com/documentation/swiftui/view/scrollbouncebehavior(_:axes:)
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
.scrollBounceBehavior(.basedOnSize)
}
}
Related
Upon navigating to a view, I want the number pad to already be raised. Right now I have a solution that works the first time (albeit with a delay) but fails to raise the number pad if the user navigates back a second time. Is there a better way to raise the number pad in SwiftUI (or to have it always up)?
Example Code:
struct ParentView: View {
#FocusState var numberPadFocused: Bool
#State var isActive: Bool = false
var body: some View {
NavigationView {
VStack {
Button {
numberPadFocused = true
isActive = true
print("Called")
} label: {
Text("Navigate")
}
NavigationLink(destination: ChildView(focusState: $numberPadFocused), isActive: $isActive) { Color.white }
}
}
}
}
struct ChildView: View {
#State var text: String = ""
#FocusState.Binding var focusState: Bool
var body: some View {
TextField("Enter Number...", text: $text)
.keyboardType(.numberPad)
.focused($focusState)
}
}
I set a variable in the contentview #State var shouldShowModal = false, i want to change it once i press a button shouldShowModal = false. I keep getting Cannot find 'shouldShowModal' in scope.
Here is a working example, passing the value through #Bindings. Read more about #Binding here, or the official documentation.
This means that you can now do shouldShowModal = false with the binding, which will also update the body of the original view containing #State.
struct ContentView: View {
#State private var shouldShowModal = false
var body: some View {
VStack {
Text("Hello world!")
.sheet(isPresented: $shouldShowModal) {
Text("Modal")
}
OtherView(shouldShowModal: $shouldShowModal)
}
}
}
struct OtherView: View {
#Binding var shouldShowModal: Bool
var body: some View {
VStack {
Text("Should show modal: \(shouldShowModal ? "yes" : "no")")
Toggle("Toggle modal", isOn: $shouldShowModal)
}
}
}
As there are some problems with iOS 13.4 and Xcode 11.4 with presentationMode.wrappedValue.dismiss() I am looking for an alternative approach to go back programmatically. I found this solution from MScottWaller:
iOS SwiftUI: pop or dismiss view programmatically
Unfortunately, in my case it does not work:
struct MasterView: View {
#State private var showDetail = false
var body: some View {
VStack {
Text("MasterView")
.navigationBarItems(trailing: HStack {
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
Image(systemName: "tag")
.padding(.leading, 4)
}
})
}
}
}
struct DetailView: View {
#Binding var showSelf: Bool
var body: some View {
Button(action: {
self.showSelf = false
}) {
Text("Pop")
}
}
}
If the NavigationLink is inside a navigationBarItem, I can't go back from my DetailView. I don't know if it is a bug or there are other reasons why NavigationLink does not work in the same way inside a navigationBarItem.
As a workaround I use this variant with a empty NavigationLink inside the view. It works, but I don't like this:
struct MasterView: View {
#State private var showDetail = false
var body: some View {
VStack {
Text("MasterView")
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
EmptyView()
}
.navigationBarItems(trailing: HStack {
Button(action: { self.showDetail.toggle() }) {
Image(systemName: "tag")
.padding(.leading, 4)
}
})
}
}
}
Any ideas why the NavigationLink does not correct work inside a navigationBarItem?
It's an iOS bug.
https://forums.developer.apple.com/thread/125937
The work around is to toggle a NavigationLink hidden outside of nav bar:
struct Parent: View {
#State private var showingChildView = false
var body: some View {
NavigationView {
VStack {
Text("Hello World")
NavigationLink(destination: Child(),
isActive: self.$showingChildView)
{ Text("HiddenLink").hidden() }
}
.navigationBarItems(trailing: Button(action:{ self.showingChildView = true }) { Text("Next") })
}
}
}
I have a screen in my app that automatically displays a keyboard when the screen is presented. The screen allows users to enter their mobile number. In order to select a dialling code, the user needs to tap a button which will then trigger the presentation of an overlay.
Problem
The overlay is being presented, but it's showing up behind the currently present keyboard.
Question
How can I make this overlay be the very top view?
There is no way for me to use the zIndex modifier on the keyboard for obvious reasons. I'm wondering if there is a way to make the overlay the top view when it's about to be presented, or if the overlay can be added to the window.
Thanks in advance
You should probably only have one source of input at any given time — either the keyboard should be presented to enter a number, or the overlay should be presented to pick the dialing code, not both. Here's an example which hides the keyboard when overlay appears, and vice versa. (Keyboard dismissal code from this answer.)
struct ContentView: View {
#State private var number = ""
#State private var showingOverlay = false
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .top) {
// Just here to force ZStack to use the whole screen
Rectangle()
.fill(Color.clear)
VStack(alignment: .leading) {
Button("Select Dialing Code") {
UIApplication.shared.endEditing()
self.showingOverlay = true
}
TextField("Enter your number", text: self.$number, onEditingChanged: {
if $0 { self.showingOverlay = false }
})
.keyboardType(.phonePad)
}
Overlay(showing: self.$showingOverlay)
.frame(height: 400)
.offset(x: 0, y: proxy.size.height + (self.showingOverlay ? -300 : 100))
.animation(.easeInOut)
}
}
}
}
struct Overlay: View {
#Binding var showing: Bool
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(Color(.systemGray4))
Button("Dismiss") {
self.showing = false
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
i have used ScrollView with HStack, now i need to load more data when user reached scrolling at last.
var items: [Landmark]
i have used array of items which i am appeding in HStack using ForEach
ScrollView(showsHorizontalIndicator: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
What is the best possible solution to manage load more in SwiftUI without using custom action like loadmore button.
It's better to use ForEach and List for this purpose
struct ContentView : View {
#State var textfieldText: String = "String "
private let chunkSize = 10
#State var range: Range<Int> = 0..<1
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("Load more")
}
}
}
func loadMore() {
print("Load more...")
self.range = 0..<self.range.upperBound + self.chunkSize
}
}
In this example each time you press load more it increases range of State property. The same you can do for BindableObject.
If you want to do it automatically probably you should read about PullDownButton(I'm not sure if it works for PullUp)
UPD:
As an option you can download new items by using onAppear modifier on the last cell(it is a static button in this example)
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("")
}
.onAppear {
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 10)) {
self.loadMore()
}
}
}
}
Keep in mind, that dispatch is necessary, because without it you will have an error saying "Updating table view while table view is updating). Possible you may using another async way to update the data
If you want to keep using List with Data instead of Range, you could implement the next script:
struct ContentView: View {
#State var items: [Landmark]
var body: some View {
List {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
.onAppear {
checkForMore(landmark)
}
}
}
}
func checkForMore(_ item: LandMark) {
guard let item = item else { return }
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
// function to request more data
getMoreLandMarks()
}
}
}
Probably you should work in a ViewModel and separate the logic from the UI.
Credits to Donny Wals: Complete example