如何在UIScrollView中始终显示滚动条?

30

我需要在viewDidLoad中使滚动条始终可见,以便用户可以了解需要滚动的内容。 我按照以下步骤操作:

[myscrollView flashScrollIndicators];

然而,滚动条仅在viewDidLoad后的一段时间内出现,随后消失,只有当用户触摸屏幕时才重新出现。

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


我不太明白你的意思,滚动视图是在触摸时出现吗?另外,滚动条是如何消失的?你设置了计时器还是跳转到下一个视图然后再返回来的? - IronManGill
是的,当我触摸屏幕时,滚动视图会出现。当我使用[myscrollView flashScrollIndicators];时,它们会在视图加载时出现,然后在几秒钟后消失。我想让它们始终可见...不,我不会来回切换。 - Honey
请查看以下内容: https://dev59.com/rHA75IYBdhLWcg3wdIp0 - Divya
4个回答

22

苹果间接地鼓励不要在 iOS 人机界面指南 中经常显示滚动指示器,但是指南只是指南,它们并不能涵盖每种情况,有时您可能需要礼貌地忽略它们。

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

最受欢迎的解决方案似乎是以下代码:

#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

这段文字的原始来源是this source
这个分类为UIImageView定义了一个自定义setter,用于alpha属性。这是因为在UIScrollView的底层代码中,在某个时刻,它会将其滚动指示器的alpha属性设置为0,以便隐藏它。此时,它将通过我们的分类,并且如果托管UIScrollView具有正确的标记,则会忽略正在设置的值,使其保持显示状态。
为了使用此解决方案,请确保您的UIScrollView具有适当的标记,例如。 Tag 如果您想要在其UIScrollView可见时立即显示滚动指示器,只需在视图出现时闪烁滚动指示器即可。
- (void)viewDidAppear:(BOOL)animate
{
    [super viewDidAppear:animate];
    [self.scrollView flashScrollIndicators];
}

其他SO参考资料:


我必须提醒一下,这个方法依赖于一个私有实现细节(即指示器是 UIImageView 对象),这个细节并不总是正确的。事实上,当你最不希望它发生时,通常在新的 iOS 版本中,这种方法往往会出现问题。因此,如果您使用这种方法,请记住,您的结果可能会有所不同。 - lxt
2
为避免在64位上出现问题,请使用(CGFloat)alpha代替(float)alpha。请参见https://dev59.com/_HrZa4cB1Zd3GeqP6seV。 - Sarah Elan
与iOS不同,在Android中我们只需要设置相关属性,而无需手动实现该功能;-) - Top-Master

9
我想提供我的解决方案。我不喜欢最流行的分类变体(在分类中覆盖方法可能是一些不确定性的原因,因为有两个具有相同选择器的方法)。 我使用交换方法而不是标签。并且我也不需要使用标签。
将这个方法添加到您的视图控制器中,其中包含滚动视图(在我的情况下是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];
}

添加新类

@interface AlwaysOpaqueImageView : UIImageView
@end

@implementation AlwaysOpaqueImageView

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

@end

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

2019年11月更新

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

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

  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. 添加新类(适用于 iOS 13 之前的版本)
@interface AlwaysOpaqueImageView : UIImageView
@end

@implementation AlwaysOpaqueImageView

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

@end
  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];
}

这一步在运行时创建了_UIScrollViewScrollIndicator的子类AlwaysOpaqueScrollIndicator,并交换了setAlpha:方法的实现以使用alwaysOpaque_setAlpha:

别忘了将

#import <objc/runtime.h>

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


1
在使用iOS 10时,这个功能是有效的。如果你想设置水平滚动条,请使用:UIViewAutoresizing.flexibleTopMargin(Swift 3.0)或UIViewAutoresizingFlexibleTopMargin(Obj-C)。 - Jacob Boyd
2
在iOS 11中运行良好。需要 #include <objc/runtime.h>。 - Smartcat

0
我不知道这个方法是否有效,但是我可以给你一个提示。
在UIScrollView内部的滚动条是UIImageView。它是UIScrollView的子视图。
所以要获取UIScrollView的滚动条UIImageView,然后尝试将该图像属性的隐藏属性设置为“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;
}

这里获得了参考


实际上,在您提供的示例中,我已经检查过了。它涉及更改滚动条的颜色。但是我需要它们在视图上始终可见。 - Honey
我知道,只是给你一个提示来获取那个视图。既然你已经得到了滚动条对象,只需通过调整其隐藏属性或alpha值来操纵它,以便始终显示。 - ipraba
我尝试了使用隐藏属性,但是没有起作用。您能告诉我如何使用 alpha 值来实现吗? - Honey
请注意。这对你来说可能只是尝试而已。当您获取滚动条对象时,请尝试修改它。但是,当滚动条发生操作时,所有属性都会被修改。因此,请在您的操作结束时对滚动条进行修改。 - ipraba

0

这是 @Accid Bright 的 answer 的 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()
    }
}

一个区别是设置滚动条被提取为静态方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接