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
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
So get the Scrollbar Imageview of the UIscrollview. Then try to set that image property hidden to NO or Change Alpha value
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
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.
The scroll indicators of any content views are UIImageView
subviews of those content views. This means you can access the scroll indicators of a UIScrollView
as you would any of its other subviews (i.e. myScrollView.subviews
) and modify the scroll indicators as you would any UIImageView
(e.g. 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. 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 UIScrollView
has the right tag, it will ignore the value being set, leaving it displayed.
In order to use this solution ensure your UIScrollView
has the appropriate tag e.g.
If you want to display the scroll indicator from the moment its UIScrollView
is visible simply flash the scroll indicators when the view appears .e.g
- (void)viewDidAppear:(BOOL)animate
{
[super viewDidAppear:animate];
[self.scrollView flashScrollIndicators];
}
Additional SO references:
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.
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:
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];
}
@interface AlwaysOpaqueImageView : UIImageView
@end
@implementation AlwaysOpaqueImageView
- (void)setAlpha:(CGFloat)alpha {
[super setAlpha:1.0];
}
@end
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 <objc/runtime.h>
to the files you've inserted this code. Thanks to @Smartcat for reminder about this
This is Swift version of @Accid Bright's answer:
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.