ios 使滚动条在 UIScrollView 上始终可见?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13697215/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:24:47  来源:igfitidea点击:

Make scrollbar always visible on UIScrollView?

iphoneioscocoa-touchuiscrollviewuikit

提问by Honey

I need to make a scrollbar always visible on viewDidLoad so that the user can understand that there is content to scroll. I did the following:

我需要让滚动条在 viewDidLoad 上始终可见,以便用户可以理解有内容要滚动。我做了以下事情:

[myscrollView flashScrollIndicators];

But then the scrollbars only appear for some time after viewDidLoad and disappear again only to reappear when the user touches the screen..

但是滚动条只会在 viewDidLoad 之后出现一段时间,然后再次消失,只有在用户触摸屏幕时才会重新出现。

I need to make scrollbars always visible. How can I do it?

我需要使滚动条始终可见。我该怎么做?

采纳答案by Elliott James Perry

Apple indirectly discourage constantly displaying scroll indicators in their iOS Human Interface Guidelinesbut guidelines are just guidelines for a reason, they don't account for every scenario and sometimes you may need to politely ignore them.

Apple 间接地不鼓励在其iOS 人机界面指南中不断显示滚动指示器,但指南只是出于某种原因的指南,它们并没有考虑到所有情况,有时您可能需要礼貌地忽略它们。

The scroll indicators of any content views are UIImageViewsubviews of those content views. This means you can accessthe scroll indicators of a UIScrollViewas you would any of its other subviews (i.e. myScrollView.subviews) and modifythe scroll indicators as you would any UIImageView(e.g. scrollIndicatorImageView.backgroundColor = [UIColor redColor];).

任何内容视图的滚动指示器都是UIImageView这些内容视图的子视图。这意味着您可以像访问a 的UIScrollView任何其他子视图(即myScrollView.subviews)一样访问 a的滚动指示器,并可以像访问任何其他子视图一样修改滚动指示器UIImageView(例如scrollIndicatorImageView.backgroundColor = [UIColor redColor];)。

The most popular solution appears to be the following code:

最流行的解决方案似乎是以下代码:

#define noDisableVerticalScrollTag 836913
#define noDisableHorizontalScrollTag 836914

@implementation UIImageView (ForScrollView) 

- (void) setAlpha:(float)alpha {

    if (self.superview.tag == noDisableVerticalScrollTag) {
        if (alpha == 0 && self.autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
            if (self.frame.size.width < 10 && self.frame.size.height > self.frame.size.width) {
                UIScrollView *sc = (UIScrollView*)self.superview;
                if (sc.frame.size.height < sc.contentSize.height) {
                    return;
                }
            }
        }
    }

    if (self.superview.tag == noDisableHorizontalScrollTag) {
        if (alpha == 0 && self.autoresizingMask == UIViewAutoresizingFlexibleTopMargin) {
            if (self.frame.size.height < 10 && self.frame.size.height < self.frame.size.width) {
                UIScrollView *sc = (UIScrollView*)self.superview;
                if (sc.frame.size.width < sc.contentSize.width) {
                    return;
                }
            }
        }
    }

    [super setAlpha:alpha];
}

@end

Which is originally credited to this source.

这最初归功于这个来源

This defines a category for UIImageViewthat defines a custom setter for the alpha property. This works because at some point in the underlying code for the UIScrollView, it will set its scroll indicator's alpha property to 0 in order to hide it. At this point it will run through our category and, if the hosting UIScrollViewhas the right tag, it will ignore the value being set, leaving it displayed.

这定义了一个类别,UIImageView为 alpha 属性定义了一个自定义设置器。这是有效的,因为在 的底层代码中UIScrollView,它会将其滚动指示器的 alpha 属性设置为 0 以隐藏它。此时它将遍历我们的类别,如果托管UIScrollView具有正确的标签,它将忽略正在设置的值,而将其显示出来。

In order to use this solution ensure your UIScrollViewhas the appropriate tag e.g. Tag

为了使用此解决方案,请确保您UIScrollView拥有合适的标签,例如 标签

If you want to display the scroll indicator from the moment its UIScrollViewis visible simply flash the scroll indicators when the view appears .e.g

如果您想从UIScrollView可见的那一刻起显示滚动指示器,只需在视图出现时闪烁​​滚动指示器即可。例如

- (void)viewDidAppear:(BOOL)animate
{
    [super viewDidAppear:animate];
    [self.scrollView flashScrollIndicators];
}

Additional SO references:

其他 SO 参考:

回答by Accid Bright

I want to offer my solution. I don't like the most popular variant with category (overriding methods in category can be the reason of some indetermination what method should be called in runtime, since there is two methods with the same selector). I use swizzling instead. And also I don't need to use tags.

我想提供我的解决方案。我不喜欢最流行的带有类别的变体(覆盖类别中的方法可能是不确定在运行时应该调用什么方法的原因,因为有两个方法具有相同的选择器)。我用 swizzling 代替。而且我不需要使用标签。

Add this method to your view controller, where you have scroll view (self.categoriesTableViewin my case)

将此方法添加到您的视图控制器中,您可以在其中滚动视图(self.categoriesTableView在我的情况下)

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // Do swizzling to turn scroll indicator always on
    // Search correct subview with scroll indicator image across tableView subviews
    for (UIView * view in self.categoriesTableView.subviews) {
        if ([view isKindOfClass:[UIImageView class]]) {
            if (view.alpha == 0 && view.autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
                if (view.frame.size.width < 10 && view.frame.size.height > view.frame.size.width) {
                    if (self.categoriesTableView.frame.size.height < self.categoriesTableView.contentSize.height) {
                        // Swizzle class for found imageView, that should be scroll indicator
                        object_setClass(view, [AlwaysOpaqueImageView class]);
                        break;
                    }
                }
            }
        }
    }
    // Ask to flash indicator to turn it on
   [self.categoriesTableView flashScrollIndicators];
}

Add new class

添加新班级

@interface AlwaysOpaqueImageView : UIImageView
@end

@implementation AlwaysOpaqueImageView

- (void)setAlpha:(CGFloat)alpha {
    [super setAlpha:1.0];
}

@end

The scroll indicator (vertical scroll indicator in this case) will be always at the screen.

滚动指示器(在这种情况下是垂直滚动指示器)将始终显示在屏幕上。

Update November, 2019

2019 年 11 月更新

Starting from iOS 13 UIScrollViewsubclasses are changed. Now scroll indicators are inherited from UIViewand has their own privateclass called _UIScrollViewScrollIndicator. This means, that they are not subclasses of UIImageViewnow, so old method won't work anymore.

从 iOS 13 开始,UIScrollView子类发生了变化。现在滚动指示器继承自UIView并拥有自己的私有类,称为_UIScrollViewScrollIndicator. 这意味着,它们不是UIImageView现在的子类,因此旧方法将不再起作用。

Also we are not able to implement subclass of _UIScrollViewScrollIndicatorbecause it is private class and we don't have access to it. So the only solution is to use runtime. Now to have support for iOS 13 and earlier implement the next steps:

此外,我们无法实现的子类,_UIScrollViewScrollIndicator因为它是私有类,我们无权访问它。所以唯一的解决方案是使用运行时。现在要支持 iOS 13 及更早版本,请执行以下步骤:

  1. Add this method to your view controller, where you have scroll view (self.categoriesTableViewin my case)
  1. 将此方法添加到您的视图控制器中,您可以在其中滚动视图(self.categoriesTableView在我的情况下)
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // Do swizzling to turn scroll indicator always on
    // Search correct subview with scroll indicator image across tableView subviews
    for (UIView * view in self.categoriesTableView.subviews) {
        if ([view isKindOfClass:[UIImageView class]]) {
            if (view.alpha == 0 && view.autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
                if (view.frame.size.width < 10 && view.frame.size.height > view.frame.size.width) {
                    if (self.categoriesTableView.frame.size.height < self.categoriesTableView.contentSize.height) {
                        // Swizzle class for found imageView, that should be scroll indicator
                        object_setClass(view, [AlwaysOpaqueImageView class]);
                        break;
                    }
                }
            }
        } else if ([NSStringFromClass(view.class) isEqualToString:@"_UIScrollViewScrollIndicator"]) {
            if (view.frame.size.width < 10 && view.frame.size.height > view.frame.size.width) {
                if (self.categoriesTableView.frame.size.height < self.categoriesTableView.contentSize.height) {
                    // Swizzle class for found scroll indicator, (be sure to create AlwaysOpaqueScrollIndicator in runtime earlier!)
                    // Current implementation is in AlwaysOpaqueScrollTableView class
                    object_setClass(view, NSClassFromString(@"AlwaysOpaqueScrollIndicator"));
                    break;
                }
            }
        }
    }
    // Ask to flash indicator to turn it on
    [self.categoriesTableView flashScrollIndicators];
}
  1. Add new class (this is for iOS earlier than 13)
  1. 添加新类(这是针对 13 之前的 iOS)
@interface AlwaysOpaqueImageView : UIImageView
@end

@implementation AlwaysOpaqueImageView

- (void)setAlpha:(CGFloat)alpha {
    [super setAlpha:1.0];
}

@end
  1. Add these methods somewhere in you code (either the same view controller as in step 1, or to the desired UIScrollViewsubclass).
  1. 在代码中的某处添加这些方法(与步骤 1 中的视图控制器相同,或者添加到所需的UIScrollView子类)。
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // Create child class from _UIScrollViewScrollIndicator since it is private
        Class alwaysOpaqueScrollIndicatorClass =  objc_allocateClassPair(NSClassFromString(@"_UIScrollViewScrollIndicator"), "AlwaysOpaqueScrollIndicator", 0);
        objc_registerClassPair(alwaysOpaqueScrollIndicatorClass);

        // Swizzle setAlpha: method of this class to custom
        Class replacementMethodClass = [self class];

        SEL originalSelector = @selector(setAlpha:);
        SEL swizzledSelector = @selector(alwaysOpaque_setAlpha:);

        Method originalMethod = class_getInstanceMethod(alwaysOpaqueScrollIndicatorClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(replacementMethodClass, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(alwaysOpaqueScrollIndicatorClass,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(alwaysOpaqueScrollIndicatorClass,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)alwaysOpaque_setAlpha:(CGFloat)alpha {
    [self alwaysOpaque_setAlpha:1.0];
}

This step creates the subclass of _UIScrollViewScrollIndicatorcalled AlwaysOpaqueScrollIndicatorin runtime and swizzle setAlpha:method implementation to alwaysOpaque_setAlpha:.

这一步创建了在运行时_UIScrollViewScrollIndicator调用的子类AlwaysOpaqueScrollIndicator并将 swizzlesetAlpha:方法实现创建到alwaysOpaque_setAlpha:.

Do not forget to add

不要忘记添加

#import <objc/runtime.h>

#import <objc/runtime.h>

to the files you've inserted this code. Thanks to @Smartcat for reminder about this

到您插入此代码的文件。感谢@Smartcat 对此的提醒

回答by green0range

This is Swift version of @Accid Bright's answer:

这是@Accid Bright答案的Swift 版本:

class AlwaysOpaqueImageView: UIImageView {

    override var alpha: CGFloat {
        didSet {
            alpha = 1
        }
    }

    static func setScrollbarToAlwaysVisible(from scrollView: UIScrollView) {
        // Do swizzling to turn scroll indicator always on
        // Search correct subview with scroll indicator image across tableView subviews
        for view in scrollView.subviews {
            if view.isKind(of: UIImageView.self),
                view.alpha == 0 && view.autoresizingMask == UIView.AutoresizingMask.flexibleLeftMargin,
                view.frame.size.width < 10 && view.frame.size.height > view.frame.size.width,
                scrollView.frame.size.height < scrollView.contentSize.height {
                // Swizzle class for found imageView, that should be scroll indicator
                object_setClass(view, AlwaysOpaqueImageView.self)
                break
            }
        }

        // Ask to flash indicator to turn it on
        scrollView.flashScrollIndicators()
    }
}

One difference is that setting scrollbar is extracted out as a static method.

一个区别是设置滚动条是作为静态方法提取出来的。

回答by iPrabu

I dont know whether this will work or not. But just a hint for you.

我不知道这是否会奏效。但只是给你一个提示。

Scrollbar inside the Scrollview is a Imageview. Which is a subview of UIScrollview

Scrollview 内的滚动条是一个 Imageview。这是 UIScrollview 的子视图

So get the Scrollbar Imageview of the UIscrollview. Then try to set that image property hidden to NO or Change Alpha value

所以得到UIscrollview的Scrollbar Imageview。然后尝试将该图像属性隐藏为 NO 或更改 Alpha 值

static const int UIScrollViewHorizontalBarIndexOffset = 0;
static const int UIScrollViewVerticalBarIndexOffset = 1;
-(UIImageView *)scrollbarImageViewWithIndex:(int)indexOffset 
{
    int viewsCount = [[yourScrollview subviews] count];
    UIImageView *scrollBar = [[yourScrollview subviews] objectAtIndex:viewsCount - indexOffset - 1];
    return scrollBar;
}

-(void) viewDidLoad
{
    //Some Code
    //Get Scrollbar
    UIImageView *scrollBar = [self scrollbarImageViewWithIndex: UIScrollViewVerticalBarIndexOffset];

    //The try setting hidden property/ alpha value
    scrollBar.hidden=NO;
}

Got reference from here

这里得到参考