简体   繁体   English

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

[英]Make scrollbar always visible on UIScrollView?

I need to make a scrollbar always visible on viewDidLoad so that the user can understand that there is content to scroll.我需要让滚动条在 viewDidLoad 上始终可见,以便用户可以理解有内容要滚动。 I did the following:我做了以下事情:

[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?我该怎么做?

Apple indirectly discourage constantly displaying scroll indicators in their iOS Human Interface Guidelines but 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 UIImageView subviews of those content views.任何内容视图的滚动指示器都是这些内容视图的UIImageView子视图。 This means you can access the scroll indicators of a UIScrollView as you would any of its other subviews (ie myScrollView.subviews ) and modify the scroll indicators as you would any UIImageView (eg scrollIndicatorImageView.backgroundColor = [UIColor redColor]; ).这意味着您可以像访问任何其他子视图(即myScrollView.subviews )一样访问UIScrollView的滚动指示器,并像访问任何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 UIImageView that defines a custom setter for the alpha property.这为UIImageView定义了一个类别,该类别为 alpha 属性定义了一个自定义设置器。 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.这是有效的,因为在UIScrollView的底层代码中,它会将其滚动指示器的 alpha 属性设置为 0 以隐藏它。 At this point it will run through our category and, if the hosting UIScrollView has the right tag, it will ignore the value being set, leaving it displayed.此时它将运行我们的类别,如果托管UIScrollView具有正确的标签,它将忽略正在设置的值,并保持显示。

In order to use this solution ensure your UIScrollView has the appropriate tag eg为了使用此解决方案,请确保您的UIScrollView具有适当的标签,例如标签

If you want to display the scroll indicator from the moment its UIScrollView is visible simply flash the scroll indicators when the view appears .eg如果您想从UIScrollView可见的那一刻起显示滚动指示器,只需在视图出现时闪烁​​滚动指示器即可。例如

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

Additional SO references:其他 SO 参考:

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.我用 swizzling 代替。 And also I don't need to use tags.而且我不需要使用标签。

Add this method to your view controller, where you have scroll view ( self.categoriesTableView in 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 UIScrollView subclasses are changed.从 iOS 13 开始UIScrollView子类发生了变化。 Now scroll indicators are inherited from UIView and has their own private class called _UIScrollViewScrollIndicator .现在滚动指示器继承自UIView并拥有自己的私有类,称为_UIScrollViewScrollIndicator This means, that they are not subclasses of UIImageView now, so old method won't work anymore.这意味着,它们现在不是UIImageView子类,因此旧方法将不再起作用。

Also we are not able to implement subclass of _UIScrollViewScrollIndicator because it is private class and we don't have access to it.此外,我们无法实现_UIScrollViewScrollIndicator子类,因为它是私有类,我们无权访问它。 So the only solution is to use runtime.所以唯一的解决方案是使用运行时。 Now to have support for iOS 13 and earlier implement the next steps:现在要支持 iOS 13 及更早版本,请执行以下步骤:

  1. Add this method to your view controller, where you have scroll view ( self.categoriesTableView in 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;
                    }
                }
            }
        } 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)添加新类(这是针对 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 UIScrollView subclass).在代码中的某处添加这些方法(与步骤 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 _UIScrollViewScrollIndicator called AlwaysOpaqueScrollIndicator in runtime and swizzle setAlpha: method implementation to alwaysOpaque_setAlpha: .此步骤在运行时创建名为AlwaysOpaqueScrollIndicator_UIScrollViewScrollIndicator的子类, _UIScrollViewScrollIndicator setAlpha:方法实现混合到alwaysOpaque_setAlpha:

Do not forget to add不要忘记添加

#import <objc/runtime.h>

to the files you've inserted this code.到您插入此代码的文件。 Thanks to @Smartcat for reminder about this感谢@Smartcat 对此的提醒

I dont know whether this will work or not.我不知道这是否会奏效。 But just a hint for you.但只是给你一个提示。

Scrollbar inside the Scrollview is a Imageview. Scrollview 内的滚动条是一个 Imageview。 Which is a subview of UIScrollview这是 UIScrollview 的子视图

So get the Scrollbar Imageview of the UIscrollview.所以得到UIscrollview的Scrollbar Imageview。 Then try to set that image property hidden to NO or Change Alpha value然后尝试将该图像属性隐藏为 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这里得到参考

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.一个区别是设置滚动条是作为静态方法提取出来的。

The only thing that worked for me was this:唯一对我有用的是:

Timer Flash Indicator Fix 定时器闪光指示器修复

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM