Make scrollbar always visible on UIScrollView?

后端 未结 4 2125
你的背包
你的背包 2020-11-30 09:50

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:

[myscrollView          


        
4条回答
  •  既然无缘
    2020-11-30 10:31

    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.

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

    - (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

    Starting from iOS 13 UIScrollView subclasses are changed. Now scroll indicators are inherited from UIView and has their own private class called _UIScrollViewScrollIndicator. This means, that they are not subclasses of UIImageView now, so old method won't work anymore.

    Also we are not able to implement subclass of _UIScrollViewScrollIndicator because 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:

    1. Add this method to your view controller, where you have scroll view (self.categoriesTableView in my case)
    - (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)
    @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).
    + (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:.

    Do not forget to add

    #import

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

提交回复
热议问题