Does anyone know a way to temporarily turn off zooming when using a UIScrollView?
I see that you can disable scrolling using the following:
self.scrollView.scrollEnabled = false;
but I'm not seeing a similar command for zooming. Any thoughts?
If you want to disable the user's ability to zoom through gestures then in iOS 5 and above you can disable the pinch gesture. This still allows you to control the scroll view from code...
scrollView.pinchGestureRecognizer.enabled = NO;
similarly for pan...
scrollView.panGestureRecognizer.enabled = NO;
This must be called in - (void)viewDidAppear:(BOOL)animated or later as otherwise the system resets it to enabled.
Swift 4.x and above:
imageZoomView.pinchGestureRecognizer?.isEnabled = false / true
Following fbrereto's advice above, I created two functions lockZoom and unlockZoom. When locking Zoom i copied my max and min zoom scales to variables then set the max and min zoom scale to 1.0. Unlocking zoom just reverses the process.
-(void)lockZoom
{
maximumZoomScale = self.scrollView.maximumZoomScale;
minimumZoomScale = self.scrollView.minimumZoomScale;
self.scrollView.maximumZoomScale = 1.0;
self.scrollView.minimumZoomScale = 1.0;
}
-(void)unlockZoom
{
self.scrollView.maximumZoomScale = maximumZoomScale;
self.scrollView.minimumZoomScale = minimumZoomScale;
}
Also you can return "nil" as zooming view in UIScrollViewDelegate:
- (UIView *) viewForZoomingInScrollView:(UIScrollView *) scrollView
{
return canZoom?view:nil;
}
Check setting minimumZoomScale and maximumZoomScale; According to the docs:
maximumZoomScale must be greater than minimumZoomScale for zooming to be enabled.
So, setting the values to be the same should disable zooming.
I have tried setting minimumZoomScale and maximumZoomScale properties of UIScrollView to 1 or isMultipleTouchEnabled property of UIView to false or return nil from viewForZooming(in:) of UIScrollViewDelegate but none worked. In my case, after several trial and error, the following works in my case [Tested on iOS 10.3]:
class MyViewController: UIViewController {
var webView: WKWebView?
override viewDidLoad() {
super.viewDidLoad()
//...
self.webView.scrollView.delegate = self
//...
}
}
extension MyViewController: UIScrollViewDelegate {
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollView.pinchGestureRecognizer?.isEnabled = false
}
}
I know this is a really old question but I made a slight variation for my purposes.
I wanted to be able to easily tell if the zooming was in fact enabled/disabled without relying on a comparison between scrollView.minimumZoomScale == scrollView.maximumZoomScale, which could possibly not reflect whether zooming was actually enabled/disabled.
So I did this
// .h
#property (assign, nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
// .m
#synthesize zoomEnabled = _zoomEnabled;
- (void)setZoomEnabled:(BOOL)zoomEnabled;
{
_zoomEnabled = zoomEnabled;
UIScrollView *scrollView = self.scrollView;
if (zoomEnabled) {
scrollView.minimumZoomScale = self.minimumZoomScale;
scrollView.maximumZoomScale = self.maximumZoomScale;
} else {
scrollView.minimumZoomScale = 0.0f;
scrollView.maximumZoomScale = 0.0f;
}
}
The values for self.minimumZoomScale and self.maximumZoomScale are set at the time the UIScrollView is configured.
This gives me the ability to set/ask if zooming is enabled.
myViewController.zoomEnabled = YES;
myViewController.isZoomEnabled;
here, my solution for stop zooming on scrollview.
self.scrollView.minimumZoomScale=self.scrollView.maximumZoomScale;
Swift 3 Version:
func lockScrollViewZooming() {
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 1.0
scrollView.bounces = false
scrollView.bouncesZoom = false
// Also, if you have double tap recognizer,
// remember to remove it
scrollView.removeGestureRecognizer(doubleTapGestureRecognizer)
}
func unlockScrollViewZooming() {
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 4.0
scrollView.bounces = true
scrollView.bouncesZoom = true
// Also, if you have double tap recognizer,
// remember to add it
scrollView.removeGestureRecognizer(doubleTapGestureRecognizer)
}
Note that doubleTapGestureRecognizer should be an instance variable. It should be similar to:
private lazy var doubleTapGestureRecognizer: UITapGestureRecognizer = {
let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
doubleTapGestureRecognizer.numberOfTapsRequired = 2
doubleTapGestureRecognizer.delegate = self
return doubleTapGestureRecognizer
}()
#objc private func handleDoubleTap(_ recognizer: UITapGestureRecognizer) {
//scrollView.setZoomScale((scrollView.zoomScale > scrollView.minimumZoomScale) ? scrollView.minimumZoomScale : scrollView.maximumZoomScale, animated: true)
if scrollView.zoomScale > scrollView.minimumZoomScale {
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
} else {
let touchLocation = recognizer.location(in: recognizer.view)
scrollView.zoom(to: CGRect(origin: touchLocation, size: CGSize(width: 22.0, height: 20.0)), animated: true)
}
}
You need to turn off Two Fingers and Double Tap of scroll view
self.scrollView.delegate = self
And
extension YourViewController: UIScrollViewDelegate {
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollView.pinchGestureRecognizer?.isEnabled = false
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return nil
}
}
If you want to disable only zooming with pinch gesture, below code does the trick.
scrollView.pinchGestureRecognizer?.requireGestureRecognizerToFail(scrollView.panGestureRecognizer)
Related
Sample project can be found at https://github.com/SRowley90/LargeTitleIssueTestiOS
I am trying to position a segmented control below the Large title in an iOS app. I have a UIToolbar which contains the segmented control inside.
When scrolling up the title and toolbar behave as expected.
When scrolling down the navigation bar is correct, but it doesn't push the UITabBar or the UITableView down, meaning the title goes above the segmented control as can be seen in the images below.
I'm pretty sure it's something to do with the constraints I have set, but I can't figure out what.
The TabBar is fixed to the top, left and right.
The TableView is fixed to the bottom, left and right.
The tableView is fixed vertically to the TabBar
I have the position UITabBarDelegate method set:
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
Take the delegation of the tableView somewhere:
tableView.delegate = self
Override the scrollViewDidScroll and update toolbar position appearance (since the real position should not change according to have that nice bounce effect.
extension ViewController: UIScrollViewDelegate {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
var verticalOffset = scrollView.contentOffset.y + defaultNavigationBarHeight
if scrollView.refreshControl?.isRefreshing ?? false {
verticalOffset += 60 // After is refreshing changes its value the toolbar goes 60 points down
print(toolbar.frame.origin.y)
}
if verticalOffset >= 0 {
toolbar.transform = .identity
} else {
toolbar.transform = CGAffineTransform(translationX: 0, y: -verticalOffset)
}
}
}
You can use the following check before applying transformation to make it more reliable and natural to default iOS style:
if #available(iOS 11.0, *) {
guard let navigationController = navigationController else { return }
guard navigationController.navigationBar.prefersLargeTitles else { return }
guard navigationController.navigationItem.largeTitleDisplayMode != .never else { return }
}
Using UIScrollViewDelegate didn't work well with CollectionView and toolbar for me. So, I did:
final class CollectionViewController: UICollectionViewController {
private var observesBag: [NSKeyValueObservation] = []
private let toolbar = UIToolbar()
override func viewDidLoad() {
super.viewDidLoad()
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let navigationBarHeight = navigationController?.navigationBar.frame.height ?? 0
let defaultNavigationBarHeight = statusBarHeight + navigationBarHeight
let observation = navigationController!
.navigationBar
.observe(\.center, options: NSKeyValueObservingOptions.new) { [weak self] navBar, _ in
guard let self = self else { return }
let newNavigatonBarHeight = navBar.frame.height + statusBarHeight
let yTranslantion = newNavigatonBarHeight - defaultNavigationBarHeight
if yTranslantion > 0 {
self.toolbar.transform = CGAffineTransform(
translationX: 0,
y: yTranslantion
)
} else {
self.toolbar.transform = .identity
}
}
observesBag.append(observation)
}
}
Observe the "center" of the navigationBar for changes and then translate the toolbar in the y-axis.
Even though it worked fine when I tried to use this solution with UIRefreshControl and Large Titles it didn't work well.
I set up the refresh control like:
private func setupRefreshControl() {
let refreshControl = UIRefreshControl()
self.webView.scrollView.refreshControl = refreshControl
}
the height of the UINavigationBar is changed after the complete refresh triggers.
I have two UICollectionVews
One of them (The parent one) is a full-screen-cell paginated collection view.
The other one (The child one) is a filter inside the "page"
Both have the same scroll direction
My problem is that when I'm scrolling the child one, and it reaches the end, the parent one starts moving. And I would like to avoid that. I tried many things
*ScrollView delegates
*Touchesbegan
Any ideas?
Thanks!
I think it's easy .
To set the parent UICollectionView
collectionView.scrollEnabled = NO;
Or it sounds unreasonable . If the parent UICollectionView could scroll, how could you archive your goal? Because UICollectionView is consisted of the cells (The child one). The cell will affect the parent UICollectionView inevitably.
Maybe The child one is located in part of the cell , you could use the
UIScrollViewDelegate's method: scrollViewDidScroll , to set the parent collectionView's scrollEnabled property.
I think you prefer this answer.
As we know a pan gesture recognizer is built in UIScrollView.
We could add an other coordinated pan gesture.
Because the apple says: 'UIScrollView's built-in pan gesture recognizer must have its
scroll view as its delegate.'
let pan = UIPanGestureRecognizer(target: self, action: #selector(ViewController.panGestureRecognizerAction(recognizer:)))
pan.delegate = self
mainScrollView.addGestureRecognizer(pan)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// So we can get the argus of the pan gesture while not affecting the scroll
after the setting.
var mainScrollEnabled = false
var subScrollEnabled = false
// Then we define two BOOL values to identify the scroll of the collectionView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
if scrollView.contentOffset.y >= maxOffsetY {
scrollView.setContentOffset(CGPoint(x: 0, y: maxOffsetY), animated: false)
mainScrollView.isScrollEnabled = false
subScrollView.isScrollEnabled = true
subScrollEnabled = true
mainScrollEnabled = false
}
}else {
if scrollView.contentOffset.y <= 0 {
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
subScrollView.isScrollEnabled = false
mainScrollView.isScrollEnabled = true
mainScrollEnabled = true
subScrollEnabled = false
}
}
}
// Then we handle the situation that the collectionView reaches the end , by the pan gesture's recognizer .
var currentPanY: CGFloat = 0
func panGestureRecognizerAction(recognizer: UIPanGestureRecognizer) {
if recognizer.state != .changed{
currentPanY = 0
// clear the data of last time after finishing the scroll
mainScrollEnabled = false
subScrollEnabled = false
}else {
let currentY = recognizer.translation(in: mainScrollView).y
// So the collectionView reaches the end.
if mainScrollEnabled || subScrollEnabled {
if currentPanY == 0 {
currentPanY = currentY //get the y
}
let offsetY = currentPanY - currentY //get the offsetY
if mainScrollEnabled {
let supposeY = maxOffsetY + offsetY
if supposeY >= 0 {
mainScrollView.contentOffset = CGPoint(x: 0, y: supposeY)
}else {
mainScrollView.contentOffset = CGPoint.zero
}
}else {
subScrollView.contentOffset = CGPoint(x: 0, y: offsetY)
}
}
}
}
I'm trying to accomplish a scenario where I can zoom in on a UIScrollView. I've seen around that the best way to accomplish this is to have an all-encompassing scrollView, which supports scrolling, and having the other UIScrollViewView inside (instead of the normal application of having a UIImageView inside)
Here's what I have:
class Editor: UIViewController, UIScrollViewDelegate {
let menuHeight = CGFloat(60)
var editor: LevelScrollView? = nil
var scrollProxy = UIScrollView()
override func viewDidLoad() {
let contentFrame = CGRectMake(0,menuHeight,self.view.frame.width,self.view.frame.height-menuHeight)
scrollProxy.delegate = self
scrollProxy.backgroundColor = UIColor.redColor()
scrollProxy.frame = contentFrame
scrollProxy.showsHorizontalScrollIndicator = false
scrollProxy.showsVerticalScrollIndicator = false
scrollProxy.bounces = false
let editor = LevelScrollView(reference: scrollProxy)
self.editor = editor
scrollProxy.addSubview(editor)
scrollProxy.contentSize = self.editor!.contentSize
scrollProxy.minimumZoomScale = 1.0
scrollProxy.zoomScale = scrollProxy.minimumZoomScale
scrollProxy.maximumZoomScale = 20.0
}
func scrollViewWillBeginZooming(scrollView: UIScrollView, withView view: UIView?) {
print("scrollViewWillBeginZooming")
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
print("scrollViewDidEndZooming")
}
func scrollViewDidZoom(scrollView: UIScrollView) {
print("scrollViewDidZoom")
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return editor
}
}
A few notes:
LevelScrollView is a UIScrollView
I removed a few things not pertaining to this issue, let me know if it would be helpful to see the entire thing.
The viewForZoomingIn delegate function is being called, with a valid UIView returning (the LevelScrollView), but the other three are never called no matter what I do.
Because of these things zooming isn't working. How would I accomplish this successfully?
Thanks!
The only solution I was able to find for this was to embed the entire scrollview in a separate UIView, and then enable zooming for that.
I would like to receive updates from the uipageviewcontroller during the page scrolling process. I want to know the transitionProgress in %. (This value should update when the user move the finger in order to get to another page). I'm interested in the animation progress from one page to another, not the progress through the total number of pages.
What I have found so far:
There is a class called UICollectionViewTransitionLayout that have the property corresponding to what I am looking for, "transitionProgress". Probably uipageviewcontroller implement this method somehow?
I can call the following method on the uipagecontroller but I only get 0 as result!
CGFloat percentComplete = [self.pageViewController.transitionCoordinator percentComplete];
in SWIFT to copy paste ;) works perfect for me
extension UIPageViewController: UIScrollViewDelegate {
public override func viewDidLoad() {
super.viewDidLoad()
for subview in view.subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
}
}
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let point = scrollView.contentOffset
var percentComplete: CGFloat
percentComplete = abs(point.x - view.frame.size.width)/view.frame.size.width
print("percentComplete: ",percentComplete)
}
}
At last I found out a solution, even if it is probably not the best way to do it:
I first add an observer on the scrollview like this:
// Get Notified at update of scrollview progress
NSArray *views = self.pageViewController.view.subviews;
UIScrollView* sW = [views objectAtIndex:0];
[sW addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
And when the observer is called:
NSArray *views = self.pageViewController.view.subviews;
UIScrollView* sW = [views objectAtIndex:0];
CGPoint point = sW.contentOffset;
float percentComplete;
//iPhone 5
if([ [ UIScreen mainScreen ] bounds ].size.height == 568){
percentComplete = fabs(point.x - 568)/568;
} else{
//iphone 4
percentComplete = fabs(point.x - 480)/480;
}
NSLog(#"percentComplete: %f", percentComplete);
I'm very happy that I found this :-)
Since I thought that the functionality of scrolling would stay forever, but that the internal implementation may change to something other than a scroll view, I found the solution below (I haven't tested this very much, but still)
NSUInteger offset = 0;
UIViewController * firstVisibleViewController;
while([(firstVisibleViewController = [self viewControllerForPage:offset]).view superview] == nil) {
++offset;
}
CGRect rect = [[firstVisibleViewController.view superview] convertRect:firstVisibleViewController.view.frame fromView:self.view];
CGFloat absolutePosition = rect.origin.x / self.view.frame.size.width;
absolutePosition += (CGFloat)offset;
(self is the UIPageViewController here, and [-viewControllerForPage:] is a method that returns the view controller at the given page)
If absolutePosition is 0.0f, then the first view controller is shown, if it's equal to 1.0f, the second one is shown, etc... This can be called repeatedly in a CADisplayLink along with the delegate methods and/or UIPanGestureRecognizer to effectively know the status of the current progress of the UIPageViewController.
EDIT: Made it work for any number of view controllers
Use this -
for (UIView *v in self.pageViewController.view.subviews) {
if ([v isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)v).delegate = self;
}
}
to implement this protocol : -(void)scrollViewDidScroll:(UIScrollView *)scrollView
and then use #xhist's code (modified) in this way
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint point = scrollView.contentOffset;
float percentComplete;
percentComplete = fabs(point.x - self.view.frame.size.width)/self.view.frame.size.width;
NSLog(#"percentComplete: %f", percentComplete);
}
Based on Appgix solution, I'm adding this directly on my 'UIPageViewController' subclass. (Since I only need it on this one)
For Swift 3:
class MYPageViewControllerSubclass: UIPageViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
for subView in view.subviews {
if subView is UIScrollView {
(subView as! UIScrollView).delegate = self
}
}
}
// MARK: - Scroll View Delegate
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let point = scrollView.contentOffset
var percentComplete: CGFloat
percentComplete = fabs(point.x - view.frame.size.width)/view.frame.size.width
NSLog("percentComplete: %f", percentComplete)
}
// OTHER CODE GOES HERE...
}
While Appgix' solution seemed to work at first, I noticed that when the user pans in a UIPageViewController, lifts the finger shortly and then immediately starts dragging again while the "snap-back" animation is NOT YET finished and then lifts his finger again (which will again "snap-back"), the scrollViewDidScroll method is only called when the page view controller finished the animation.
For the progress calculation this means the second pan produces continuous values like 0.11, 0.13, 0.16 but when the scroll view snaps back the next progress value will be 1.0 which causes my other scroll view to be out of sync.
To fight this I'm now listening to the scroll view's contentOffset key, which is still updated continuously in this situation.
KVO approach for Swift 4
var myContext = 0
override func viewDidLoad() {
for view in self.view.subviews {
if view is UIScrollView {
view.addObserver(self, forKeyPath: "contentOffset", options: .new, context: &introPagingViewControllerContext)
}
}
}
// MARK: KVO
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?)
{
guard let change = change else { return }
if context != &myContext {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if keyPath == "contentOffset" {
if let contentOffset = change[NSKeyValueChangeKey.newKey] as? CGPoint {
let screenWidth = UIScreen.main.bounds.width
let percent = abs((contentOffset.x - screenWidth) / screenWidth)
print(percent)
}
}
}
How can we change color of UIScrollview's scroll indicator to something like blue, green etc.
I know we can change it to white, black. But other then these colors.
Many Thanks
Unfortunately you can't, of course you can always roll your own. These are your options:
UIScrollViewIndicatorStyleDefault:
The default style of scroll indicator, which is black with a white border. This style is good against any content background.
UIScrollViewIndicatorStyleBlack:
A style of indicator which is black and smaller than the default style. This style is good against a white content background.
UIScrollViewIndicatorStyleWhite:
A style of indicator is white and smaller than the default style. This style is good against a black content background.
Here's more safe Swift 3 method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let verticalIndicator = scrollView.subviews.last as? UIImageView
verticalIndicator?.backgroundColor = UIColor.green
}
Both UIScrollView indicator are sub view of UIScrollView. So, we can
access subview of UIScrollView and change the property of subview.
1 .Add UIScrollViewDelegate
#interface ViewController : UIViewController<UIScrollViewDelegate>
#end
2. Add scrollViewDidScroll in implementation section
-(void)scrollViewDidScroll:(UIScrollView *)scrollView1
{
//get refrence of vertical indicator
UIImageView *verticalIndicator = ((UIImageView *)[scrollView.subviews objectAtIndex:(scrollView.subviews.count-1)]);
//set color to vertical indicator
[verticalIndicator setBackgroundColor:[UIColor redColor]];
//get refrence of horizontal indicator
UIImageView *horizontalIndicator = ((UIImageView *)[scrollView.subviews objectAtIndex:(scrollView.subviews.count-2)]);
//set color to horizontal indicator
[horizontalIndicator setBackgroundColor:[UIColor blueColor]];
}
Note:- Because these indicator update every time when you scroll
(means reset to default). SO, we put this code in scrollViewDidScroll
delegate method.
Demo available on GitHub - https://github.com/developerinsider/UIScrollViewIndicatorColor
Based on the answer of #Alex (https://stackoverflow.com/a/58415249/3876285), I'm posting just a little improvement to change the color of scroll indicators.
extension UIScrollView {
var scrollIndicators: (horizontal: UIView?, vertical: UIView?) {
guard self.subviews.count >= 2 else {
return (horizontal: nil, vertical: nil)
}
func viewCanBeScrollIndicator(view: UIView) -> Bool {
let viewClassName = NSStringFromClass(type(of: view))
if viewClassName == "_UIScrollViewScrollIndicator" || viewClassName == "UIImageView" {
return true
}
return false
}
let horizontalScrollViewIndicatorPosition = self.subviews.count - 2
let verticalScrollViewIndicatorPosition = self.subviews.count - 1
var horizontalScrollIndicator: UIView?
var verticalScrollIndicator: UIView?
let viewForHorizontalScrollViewIndicator = self.subviews[horizontalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForHorizontalScrollViewIndicator) {
horizontalScrollIndicator = viewForHorizontalScrollViewIndicator.subviews[0]
}
let viewForVerticalScrollViewIndicator = self.subviews[verticalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForVerticalScrollViewIndicator) {
verticalScrollIndicator = viewForVerticalScrollViewIndicator.subviews[0]
}
return (horizontal: horizontalScrollIndicator, vertical: verticalScrollIndicator)
}
}
If you don't add .subviews[0], you will get the deeper view and when you try to change the color of the indicator, this will appear with a weird white effect. That's because there is another view in front of it:
By adding .subviews[0] to each indicator view, once you try to change the color by calling:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
DispatchQueue.main.async() {
scrollView.scrollIndicators.vertical?.backgroundColor = UIColor.yourcolor
}
}
You will access to the first view and change the color properly:
Kudos to #Alex who posted a great solution 👍
in IOS 13
Try this one
func scrollViewDidScroll(_ scrollView: UIScrollView){
if #available(iOS 13, *) {
(scrollView.subviews[(scrollView.subviews.count - 1)].subviews[0]).backgroundColor = UIColor.themeColor(1.0) //verticalIndicator
(scrollView.subviews[(scrollView.subviews.count - 2)].subviews[0]).backgroundColor = UIColor.themeColor(1.0) //horizontalIndicator
} else {
if let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as? UIImageView) {
verticalIndicator.backgroundColor = UIColor.themeColor(1.0)
}
if let horizontalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 2)] as? UIImageView) {
horizontalIndicator.backgroundColor = UIColor.themeColor(1.0)
}
}
}
Swift 2.0 :
Add UIScrollView Delegate.
func scrollViewDidScroll(scrollView: UIScrollView){
let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as! UIImageView)
verticalIndicator.backgroundColor = UIColor.greenColor()
let horizontalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 2)] as! UIImageView)
horizontalIndicator.backgroundColor = UIColor.blueColor()
}
Try this it would certainly help you
for ( UIView *view in scrollBar.subviews ) {
if (view.tag == 0 && [view isKindOfClass:UIImageView.class])
{
UIImageView *imageView = (UIImageView *)view;
imageView.backgroundColor = [UIColor yellowColor];
}
}
Explanation: UIScrollBar is a collection of subviews. Here scrollBar indicator(vertical/horizontal) is the one of the subviews and it's an UIImageView.So if we set custom color to the UIImageView it effects scrollBar Indicator.
You can change an image of indicator, but you should do this repeadeatly
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.chageScrollIndicator()
}
func chageScrollIndicator (){
if let indicator = self.collection.subviews.last as? UIImageView {
let edge = UIEdgeInsets(top: 1.25,
left: 0,
bottom: 1.25,
right: 0)
indicator.image = UIImage(named: "ScrollIndicator")?.withRenderingMode(.alwaysTemplate).resizableImage(withCapInsets: edge)
indicator.tintColor = UIConfiguration.textColor
}
}
You can use this 2 image as template:
in IOS 13
Since iOS13 scroll indicators have class _UIScrollViewScrollIndicator, not UIImageView.
Many people used code like
let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as! UIImageView)
It's not good idea, because they promised that last subview will be UIImageView :). Now it's not and they can get crash.
You can try following code to get scrollView indicators:
extension UIScrollView {
var scrollIndicators: (horizontal: UIView?, vertical: UIView?) {
guard self.subviews.count >= 2 else {
return (horizontal: nil, vertical: nil)
}
func viewCanBeScrollIndicator(view: UIView) -> Bool {
let viewClassName = NSStringFromClass(type(of: view))
if viewClassName == "_UIScrollViewScrollIndicator" || viewClassName == "UIImageView" {
return true
}
return false
}
let horizontalScrollViewIndicatorPosition = self.subviews.count - 2
let verticalScrollViewIndicatorPosition = self.subviews.count - 1
var horizontalScrollIndicator: UIView?
var verticalScrollIndicator: UIView?
let viewForHorizontalScrollViewIndicator = self.subviews[horizontalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForHorizontalScrollViewIndicator) {
horizontalScrollIndicator = viewForHorizontalScrollViewIndicator
}
let viewForVerticalScrollViewIndicator = self.subviews[verticalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForVerticalScrollViewIndicator) {
verticalScrollIndicator = viewForVerticalScrollViewIndicator
}
return (horizontal: horizontalScrollIndicator, vertical: verticalScrollIndicator)
}
}
If you need only one (h or v indicator) - it's better to cut this func and keep only one you need (to improve perfomance).
Also it would be good to call update func inside of DispatchQueue, to keep smoothness of scrolling.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
DispatchQueue.main.async {
scrollView.updateCustomScrollIndicatorView()
}
}
This is how the color of the scroll bar is changed:
//scroll view
UIScrollView *scView = [[UIScrollView alloc] init];
scView.frame = self.view.bounds; //scroll view occupies full parent views
scView.contentSize = CGSizeMake(400, 800);
scView.backgroundColor = [UIColor lightGrayColor];
scView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
scView.showsHorizontalScrollIndicator = NO;
scView.showsVerticalScrollIndicator = YES;
scView.scrollEnabled = YES;
[self.view addSubview: scView];
If you wish to add image as well, here is the code for Swift 3
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let verticalIndicator = scrollView.subviews.last as? UIImageView
verticalIndicator?.image = UIImage(named: "imageName")
}
This works for UITableView and UICollectionView as well.
I wrote an article about this not so far ago. Unfortunately color of this bars defined by pre-defined images, so if you are going to change the color of bars some extra work will be required. Take a look to following link, you will definitely find an answer here since I tried to solve the same issue.
http://leonov.co/2011/04/uiscrollviews-scrollbars-customization/
I ran into the same problem recently so I decided to write a category for it.
https://github.com/stefanceriu/UIScrollView-ScrollerAdditions
[someScrollView setVerticalScrollerTintColor:someColor];
[someScrollView setHorizontalScrollerTintColor:someColor];`
It blends it with the original image so only the color will change. On the other hand, it can also be modified to provide a custom image for the scrollers to use.
Here is what I did in Swift 4, similar to previous answers. In my case I'm recoloring the image to be invisible, set correct corner radius and only execute this process once.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let color = UIColor.red
guard
let verticalIndicator = scrollView.subviews.last as? UIImageView,
verticalIndicator.backgroundColor != color,
verticalIndicator.image?.renderingMode != .alwaysTemplate
else { return }
verticalIndicator.layer.masksToBounds = true
verticalIndicator.layer.cornerRadius = verticalIndicator.frame.width / 2
verticalIndicator.backgroundColor = color
verticalIndicator.image = verticalIndicator.image?.withRenderingMode(.alwaysTemplate)
verticalIndicator.tintColor = .clear
}
please use below code on iOS Renderer
private bool _layouted;
public override void LayoutSubviews()
{
base.LayoutSubviews();
if (!_layouted)
{
this.Layer.BorderColor = UIColor.Red.CGColor;
var Verticalbar = (UIImageView)this.Subviews[this.Subviews.Length - 1];
Verticalbar.BackgroundColor = Color.FromHex("#0099ff").ToUIColor();
var Horizontlebar = (UIImageView)this.Subviews[this.Subviews.Length - 2];
Horizontlebar.BackgroundColor = Color.FromHex("#0099ff").ToUIColor();
_layouted = true;
}
}
As for iOS 13 subviews changed so adding simple if, solved this issues.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 13.0) {
UIView *verticalIndicator = [scrollView.subviews lastObject];
verticalIndicator.backgroundColor = [UIColor redColor];
} else {
UIImageView *verticalIndicator = [scrollView.subviews lastObject];
verticalIndicator.backgroundColor = [UIColor redColor];
}
}
You can use custom UIScrollView scrollBars to implement color in scrollbars. For more details look here