How to Only Disable Scroll in Scrollview But Not Content View

How to only disable scroll in ScrollView but not content view?

Only pass .horizontal as the scroll axis if you want the view to scroll. Otherwise, pass the empty set.

struct TestView: View {
@Binding var shouldScroll: Bool

var body: some View {
ScrollView(axes, showsIndicators: false) {
Text("Your content here")
}
}

private var axes: Axis.Set {
return shouldScroll ? .horizontal : []
}
}

Disable the scrolling but not the content (iOS 13 and higher)

Don't forget to provide a minimum reproducible example something we can copy and paste to ensure that the integrity if what you want is kept.

If your *StepView.count is static the code below might work for you

struct ButtonScroll: View {
@State var myId: Int = 0

init() {
//Add this
//It does NOT work with ScrollView
UIScrollView.appearance().isScrollEnabled = false
}
var body: some View {
VStack{
//Simulates myId changing
Button("change-position", action: {
myId = Int.random(in: 0...3)
print(myId.description)
})
ZStack{
//TabView does not allow for changes in Tab/Page count nicely
TabView(selection: $myId){
//Used Button to test interaction
Button("FirstStepView", action: {
print("FirstStepView")
}).tag(0)
Button("SecondStepView", action: {
print("SecondStepView")
}).tag(1)
Button("ThirdStepView", action: {
print("ThirdStepView")
}).tag(2)
Button("FourthStepView", action: {
print("FourthStepView")
}).tag(3)
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.animation(
Animation.easeOut(duration: 1)
)
}
}
}
}

How i can disable Scroll on Content inside of two scrollview

i have found an answer, we can handle it with ref , check for demo link
https://snack.expo.dev/@mirza009/table

SwfitUI List Make Scrolling disabled

Instead of using List which is scrollable, use ForEach.

ForEach is not scrollable by default.

Here is an example using ForEach

struct ContentView: View {
let colors: [Color] = [.red, .green, .blue]

var body: some View {
VStack {
ForEach(colors, id: \.self) { color in
Text(color.description.capitalized)
.padding()
.background(color)
}
}
}
}

A good resource for ForEach can be found here similarly one for List can be found here.



Update

There are currently two ways to stop a List from scrolling:

  • Using UITableView.appearance().isScrollEnabled = false (but this can unintended side-effects)
  • Using the 3rd party dependency Introspect

Caveats

List contained in a NavigationView

If your List is contained in a NavigationView then it will still scroll. Neither of the above methods stop it from scrolling.

Here is an example view:

import SwiftUI
import Introspect

struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<10, id: \.self) { _ in
NavigationLink("Tap") {
Text("Hello").onTapGesture {
print("hello")
}
}
}
}
// .onAppear {
// UIScrollView.appearance().isScrollEnabled = false
// }
// .introspectTableView { tableView in
// tableView.isScrollEnabled = false
// }
}
}
}

Uncommenting the onAppear or the introspectTableView does not stop the List from scrolling.

List not contained in a NavigationView

If the List is not contained in a NavigationView then we can be possible to stop the List from scrolling.

If we remove the NavigationView from the above example, we can see that by uncommenting the onAppear or the introspectTableView the List does stop scrolling.

Here is a video of the above situations.

Disable vertical scroll in UIScrollView Swift

Since iOS 11 content insets can get adjusted automatically. Try setting your scrollview's behaviour to not adjust it.

if #available(iOS 11, *) {
scrollview.contentInsetAdjustmentBehavior = .never
}

Disable ScrollView Programmatically?

Several points to begin with:

  1. You cannot disable the scrolling of a ScrollView. You would need to extend to ScrollView and override the onTouchEvent method to return false when some condition is matched.
  2. The Gallery component scrolls horizontally regardless of whether it is in a ScrollView or not - a ScrollView provides only vertical scrolling (you need a HorizontalScrollView for horizontal scrolling)
  3. You seem to say you have a problem with the image stretching itself -- this has nothing to do with the ScrollView, you can change how an ImageView scales with the android:scaleType property (XML) or the setScaleType method - for instance ScaleType.CENTER will not stretch your image and will center it at it's original size

You could modify ScrollView as follows to disable scrolling

class LockableScrollView extends ScrollView {

...

// true if we can scroll (not locked)
// false if we cannot scroll (locked)
private boolean mScrollable = true;

public void setScrollingEnabled(boolean enabled) {
mScrollable = enabled;
}

public boolean isScrollable() {
return mScrollable;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// if we can scroll pass the event to the superclass
return mScrollable && super.onTouchEvent(ev);
default:
return super.onTouchEvent(ev);
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Don't do anything with intercepted touch events if
// we are not scrollable
return mScrollable && super.onInterceptTouchEvent(ev);
}

}

You would then use

<com.mypackagename.LockableScrollView 
android:id="@+id/QuranGalleryScrollView"
android:layout_height="fill_parent"
android:layout_width="fill_parent">

<Gallery android:id="@+id/Gallery"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="horizontal">
</Gallery>

</com.mypackagename.LockableScrollView>

in your XML file (just changed the ScrollView to your special LockableScrollView).

Then call

((LockableScrollView)findViewById(R.id.QuranGalleryScrollView)).setScrollingEnabled(false);

to disable scrolling of the view.

I think that you have more than just the issue of disabling scrolling though to achieve your desired result (the gallery will remain scrollable with the above code for instance) - I'd recommend doing some more research on each of the three components (Gallery, ScrollView, ImageView) to see what properties each one has and how it behaves.

Disable Scrolling of UIScrollView on part of the ScrollView.frame

Here is one way this can be achieved, instead of disabling a specific frame, you can disable interaction on over a specific view:

  1. SubClass the UIView you add on button tap with no real implementation but just for recognition - you can also use view tags if you prefer
  2. SubClass the UIScrollView and implement touchesShouldCancel to cancel scrolling when interacting with a specific view
  3. Set scrollView.delaysContentTouches = false

Implementation

First I tried to recreate your situation, I created the child view controller class with a button which will go into the scrollview:

class ChildVC: UIViewController
{
let button = UIButton(type: .system)

override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .yellow
configureButton()
}

private func configureButton()
{
button.setTitle("Add View", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self,
action: #selector(didTapAddView),
for: .touchUpInside)
view.addSubview(button)

view.addConstraints([

button.leadingAnchor.constraint(equalTo: view.leadingAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor),
button.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.heightAnchor.constraint(equalToConstant: 50)

])
}

@objc
private func didTapAddView()
{
let customView = UIView()
customView.backgroundColor = .white
customView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(customView)

view.addConstraints([

customView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
constant: 16),
customView.topAnchor.constraint(equalTo: view.topAnchor,
constant: 16),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
constant: -16),
customView.bottomAnchor.constraint(equalTo: button.topAnchor,
constant: -16)

])
}
}

Then I created your container view controller with the scroll view and embedded the child vc into the view controller:

class ScrollTestViewController: UIViewController
{
private let scrollView = UIScrollView()
private let childVC1 = ChildVC()
private let childVC2 = ChildVC()

private let childVCHeight: CGFloat = 250
private let childVCWidth: CGFloat = UIScreen.main.bounds.width

override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .white
title = "Scroll Test"
configureScrollView()
configureChildVC1()
configureChildVC2()
}

override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: childVC1.view.bounds.width * 2,
height: scrollView.bounds.height)
}

private func configureScrollView()
{
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)

view.addConstraints([

scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)

])
}

private func configureChildVC1()
{
addChild(childVC1)
childVC1.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(childVC1.view)

view.addConstraints([

childVC1.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
childVC1.view.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
childVC1.view.widthAnchor.constraint(equalToConstant: childVCWidth),
childVC1.view.heightAnchor.constraint(equalToConstant: childVCHeight)

])
}

private func configureChildVC2()
{
addChild(childVC2)
childVC2.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(childVC2.view)

view.addConstraints([

childVC2.view.leadingAnchor.constraint(equalTo: childVC1.view.trailingAnchor),
childVC2.view.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
childVC2.view.widthAnchor.constraint(equalToConstant: childVCWidth),
childVC2.view.heightAnchor.constraint(equalToConstant: childVCHeight)

])
}
}

After doing this, you will get this result:

Child View controller swift iOS embed view controller in UIScrollView

No different, user can scroll anywhere even when swiping on top of the newly added view.

So I made these changes:

  1. Create a custom view subclass so I can recognize which view I am dragging over
class CustomView: UIView
{

}

  1. Create a custom scrollview to disable interaction over specific views
fileprivate class CustomScrollView: UIScrollView
{
override func touchesShouldCancel(in view: UIView) -> Bool
{
// Just for demo, I added this
if view is CustomView
{
print("Scroll disabled, tapping custom view")
}
else
{
print("Scroll enabled")
}

// You only need this line
return !(view is CustomView)
}
}

  1. Make the changes in the container view controller with the scroll view
// Replace private let scrollView = UIScrollView() with
private let scrollView = CustomScrollView()

// Add this as well
scrollView.delaysContentTouches = false

  1. Make changes in the child view controllers to use Custom view when tapping the button
@objc
private func didTapAddView()
{
let customView = CustomView()
customView.backgroundColor = .white
customView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(customView)

view.addConstraints([

customView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
constant: 16),
customView.topAnchor.constraint(equalTo: view.topAnchor,
constant: 16),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
constant: -16),
customView.bottomAnchor.constraint(equalTo: button.topAnchor,
constant: -16)

])
}

Now when you swipe over the view, it will prevent you from scrolling

Prevent Disable scroll in UIScrollView when swiping scrolling in UIView swift iOS

Update based on Mihai's (OP's) comments

Do you have any idea how I could do it for a UIViewController instead
of UIView, Im asking because the view is currently UIViewController
and I lose some specifications about it in transforming it to an
UIView.

So on the button press the view will add a UIViewController instead of
a UIView

A scrollview can add UIViews to it's view hierarchy therefore you can only check if the view you are scrolling on is a specific type of view.

However, I still think the solution can work if you want to add a view controller on a button tap instead of a UIView.

  1. Keep the custom view as it is:
fileprivate class CustomView: UIView
{

}

  1. Create a view controller subclass and change the default view to be the custom view subclass
fileprivate class PurpleVC: UIViewController
{
override func loadView()
{
super.loadView()

// Change the default view from a UIView
// to be of type CustomView
view = CustomView()

view.backgroundColor = .purple
}
}

  1. Add the new view controller on button tap instead of UIView
@objc
private func didTapAddView()
{
let customVC = PurpleVC()
customVC.view.translatesAutoresizingMaskIntoConstraints = false
addChild(customVC)
view.addSubview(customVC.view)

view.addConstraints([

customVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor,
constant: 16),
customVC.view.topAnchor.constraint(equalTo: view.topAnchor,
constant: 16),
customVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor,
constant: -16),
customVC.view.bottomAnchor.constraint(equalTo: button.topAnchor,
constant: -16)

])
}

  1. No change to custom scroll view as it should still detect the custom view which is the main view inside your view controller
fileprivate class CustomScrollView: UIScrollView
{
override func touchesShouldCancel(in view: UIView) -> Bool
{
// Just for demo, I added this
if view is CustomView
{
print("Scroll disabled, tapping custom view")
}
else
{
print("Scroll enabled")
}

// You only need this line
return !(view is CustomView)
}
}


Related Topics



Leave a reply



Submit