31

I'm a new learner of ios programming. I have tried to search with another example and more questions at stackoverflow but it's not my goal. I want to set an image of dot at index 0 of UIPageControl as similar as iPhone search homescreen. Have any way to do it ? Please explain me with some code or another useful link.

Thanks in advance

enter image description here

Sovannarith
  • 614
  • 1
  • 10
  • 20
  • 4
    This solution is certainly more heavy-handed, but [SMPageControl](https://github.com/Spaceman-Labs/SMPageControl) makes things like this really easy :) – Michael Mior Jun 18 '13 at 23:20
  • This works well. Just need to remember to set the number of pages first, before trying to customize specific page images. AND, it doesn't glitch out the `UIView` animation already occurring on the screen, like some of the code samples below do. – James Perih Dec 05 '15 at 06:32

11 Answers11

34

I have found a solution for this problem. I know it is not the way but it will work till iOS 8 will be launched in the market.

Reason for Crash:

in iOS 7 [self.subViews objectAtIndex: i] returns UIView Instead of UIImageView and setImage is not the property of UIView and the app crashes. I solve my problem using this following code.

Check Whether the subview is UIView(for iOS7) or UIImageView(for iOS6 or earlier). And If it is UIView I am going to add UIImageView as subview on that view and voila its working and not crash..!!

-(void) updateDots
{
    for (int i = 0; i < [self.subviews count]; i++)
    {
        UIImageView * dot = [self imageViewForSubview:  [self.subviews objectAtIndex: i]];
        if (i == self.currentPage) dot.image = activeImage;
        else dot.image = inactiveImage;
    }
}
- (UIImageView *) imageViewForSubview: (UIView *) view
{
    UIImageView * dot = nil;
    if ([view isKindOfClass: [UIView class]])
    {
        for (UIView* subview in view.subviews)
        {
            if ([subview isKindOfClass:[UIImageView class]])
            {
                dot = (UIImageView *)subview;
                break;
            }
        }
        if (dot == nil)
        {
            dot = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, view.frame.size.width, view.frame.size.height)];
            [view addSubview:dot];
        }
    }
    else
    {
        dot = (UIImageView *) view;
    }

    return dot;
}

Also, to clear the images that are already there, set the tint colors for the existing indicators to transparent:

- (void)awakeFromNib
{   
    self.pageIndicatorTintColor = [UIColor clearColor];
    self.currentPageIndicatorTintColor = [UIColor clearColor];
}

Hope this will solve ur issue for iOS 7.

Happy coding

Sahil Mahajan
  • 3,922
  • 2
  • 29
  • 43
15

Try this link:-

Answer with GrayPageControl:- Is there a way to change page indicator dots color

It is really good and reliable.I also have used this code.

You might have to do some more customization as

-(void) updateDots
{
    for (int i = 0; i < [self.subviews count]; i++)
    {
        UIImageView* dot = [self.subviews objectAtIndex:i];

        if (i == self.currentPage) {
            if(i==0) {
                dot.image = [UIImage imageNamed:@"activesearch.png"];
            } else {
                dot.image = activeImage;
            }        
        } else {
            if(i==0) {
                dot.image = [UIImage imageNamed:@"inactivesearch.png"];
            } else {
                dot.image = inactiveImage;
            }
        }
    }
}
Community
  • 1
  • 1
Gypsa
  • 11,230
  • 6
  • 44
  • 82
10

Simply change the UIPageControl page indicator Color with pattern Image self.pageControl.currentPageIndicatorTintColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"circle"]];

Kiran Patil
  • 402
  • 5
  • 13
9

The best compilation of the code for Swift 3 to replace the first icon of UIPageControl by a location marker:

import UIKit

class LocationPageControl: UIPageControl {

    let locationArrow: UIImage = UIImage(named: "locationArrow")!
    let pageCircle: UIImage = UIImage(named: "pageCircle")!

    override var numberOfPages: Int {
        didSet {
            updateDots()
        }
    }

    override var currentPage: Int {
        didSet {
            updateDots()
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        self.pageIndicatorTintColor = UIColor.clear
        self.currentPageIndicatorTintColor = UIColor.clear
        self.clipsToBounds = false
    }

    func updateDots() {
        var i = 0
        for view in self.subviews {
            var imageView = self.imageView(forSubview: view)
            if imageView == nil {
                if i == 0 {
                    imageView = UIImageView(image: locationArrow)
                } else {
                    imageView = UIImageView(image: pageCircle)
                }
                imageView!.center = view.center
                view.addSubview(imageView!)
                view.clipsToBounds = false
            }
            if i == self.currentPage {
                imageView!.alpha = 1.0
            } else {
                imageView!.alpha = 0.5
            }
            i += 1
        }
    }

    fileprivate func imageView(forSubview view: UIView) -> UIImageView? {
        var dot: UIImageView?
        if let dotImageView = view as? UIImageView {
            dot = dotImageView
        } else {
            for foundView in view.subviews {
                if let imageView = foundView as? UIImageView {
                    dot = imageView
                    break
                }
            }
        }
        return dot
    }
}

Attached images:

enter image description here enter image description here

Dmitry
  • 14,306
  • 23
  • 105
  • 189
  • The default page control icon and the replaced image both are visible what to do? – Neha Jun 26 '17 at 10:39
  • 1
    Finally, iOS 14 made life easy with the new UIPageControl APIs, set different image based on your page => pageControl.setIndicatorImage(UIImage(named: "location.png"), forPage: 0) – Soumen Sep 15 '20 at 15:59
8

I've created a custom page controller that should function in mostly the same way without hacking into the internals of a UIPageControl or having a whole library for one small widget.

Just place an empty UIStackView in your storyboard and make its custom class this class below, and use numberOfPages and currentPage just like a normal UIPageControl. Set the spacing on the UIStackView to change how much space there is between the views.

Swift 4.2

/**
 If adding via storyboard, you should not need to set a width and height constraint for this view,
 just set a placeholder for each so autolayout doesnt complain and this view will size itself once its populated with pages at runtime
 */
class PageControl: UIStackView {

    @IBInspectable var currentPageImage: UIImage = UIImage(named: "whiteCircleFilled")!
    @IBInspectable var pageImage: UIImage = UIImage(named: "whiteCircleOutlined")!
    /**
     Sets how many page indicators will show
     */
    var numberOfPages = 3 {
        didSet {
            layoutIndicators()
        }
    }
    /**
     Sets which page indicator will be highlighted with the **currentPageImage**
     */
    var currentPage = 0 {
        didSet {
            setCurrentPageIndicator()
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        axis = .horizontal
        distribution = .equalSpacing
        alignment = .center

        layoutIndicators()
    }

    private func layoutIndicators() {

        for i in 0..<numberOfPages {

            let imageView: UIImageView

            if i < arrangedSubviews.count {
                imageView = arrangedSubviews[i] as! UIImageView // reuse subview if possible
            } else {
                imageView = UIImageView()
                addArrangedSubview(imageView)
            }

            if i == currentPage {
                imageView.image = currentPageImage
            } else {
                imageView.image = pageImage
            }
        }

        // remove excess subviews if any
        let subviewCount = arrangedSubviews.count
        if numberOfPages < subviewCount {
            for _ in numberOfPages..<subviewCount {
                arrangedSubviews.last?.removeFromSuperview()
            }
        }
    }

    private func setCurrentPageIndicator() {

        for i in 0..<arrangedSubviews.count {

            let imageView = arrangedSubviews[i] as! UIImageView

            if i == currentPage {
                imageView.image = currentPageImage
            } else {
                imageView.image = pageImage
            }
        }
    }
}

Works for my purposes but I make no guarantees

Fonix
  • 11,447
  • 3
  • 45
  • 74
4

Update for Swift 3.0 ... you know if you are OK with accepting stated risk: "Modifying the subviews of an existing control is fragile".

 import UIKit

 class CustomImagePageControl: UIPageControl {

   let activeImage:UIImage = UIImage(named: "SelectedPage")!
   let inactiveImage:UIImage = UIImage(named: "UnselectedPage")!

   override func awakeFromNib() {
         super.awakeFromNib()

         self.pageIndicatorTintColor = UIColor.clear
         self.currentPageIndicatorTintColor = UIColor.clear
         self.clipsToBounds = false
    }

    func updateDots() {
         var i = 0
         for view in self.subviews {
             if let imageView = self.imageForSubview(view) {
                 if i == self.currentPage {
                     imageView.image = self.activeImage
                 } else {
                     imageView.image = self.inactiveImage
                 }
                 i = i + 1
             } else {
                 var dotImage = self.inactiveImage
                 if i == self.currentPage {
                     dotImage = self.activeImage
                 }
                 view.clipsToBounds = false
                 view.addSubview(UIImageView(image:dotImage))
                 i = i + 1
             }
         }
     }

     fileprivate func imageForSubview(_ view:UIView) -> UIImageView? {
         var dot:UIImageView?

         if let dotImageView = view as? UIImageView {
             dot = dotImageView
         } else {
             for foundView in view.subviews {
                 if let imageView = foundView as? UIImageView {
                     dot = imageView
                     break
                 }
             }
         }

         return dot
     }
 }
CodenameDuchess
  • 1,221
  • 18
  • 24
  • It will be much better to call `updateDots()` from `numberOfPages` and `currentPage` observers: http://stackoverflow.com/a/41136917/865475 – Dmitry Dec 14 '16 at 07:21
  • I like that idea. I will give it a try. Thanks – CodenameDuchess Dec 15 '16 at 15:10
  • in what place `updateDots` needs be called? i'm calling it inside `viewDidLoad` and not works. – jose920405 May 24 '18 at 12:47
  • Try calling updateDots() in viewDidAppear() and your valueChanged handler for the page control. Alternatively you can call also try calling it from numberOfPages and currentPage observers as Dmitry suggested. – CodenameDuchess May 24 '18 at 13:05
2

You just need do it like this:

((UIImageView *)[[yourPageControl subviews] objectAtIndex:0]).image=[UIImage imageNamed:@"search.png"];
Jonas
  • 71
  • 2
  • 3
    Modifying the subviews of an existing control is fragile and should be avoided. – David Rönnqvist Aug 30 '12 at 05:19
  • @DavidRönnqvist,I agree this,[DDPageControl](https://github.com/ddeville/DDPageControl) maybe a better way. – Jonas Aug 30 '12 at 05:26
  • Thanks Jonas , but the picture is very small. How we make this picture bigger than before ? the size is equal to the default dot of iPhone. – Sovannarith Aug 30 '12 at 06:14
  • 3
    on iOS7 it crashes, because the subviews are no UIImageViews anymore!!! Be aware of such a implementation!!! – Fab1n Sep 24 '13 at 12:24
1

I think for this you need to customize whole UIPageControl. Please find more out at below links

  1. How can i change the color of pagination dots of UIPageControl?

  2. http://iphoneappcode.blogspot.in/2012/03/custom-uipagecontrol.html

Community
  • 1
  • 1
BornCoder
  • 1,046
  • 8
  • 10
1

From iOS 14 you can get and set the indicator image with these methods:

@available(iOS 14.0, *)
open func indicatorImage(forPage page: Int) -> UIImage?


@available(iOS 14.0, *)
open func setIndicatorImage(_ image: UIImage?, forPage page: Int)
atxe
  • 5,029
  • 2
  • 36
  • 50
0

From iProgrammer's answer
In case you want to hide the original dot

- (UIImageView *)imageViewForSubview:(UIView *)view {
    UIImageView * dot = nil;
    [view setBackgroundColor:[UIColor clearColor]]; << add this line
saranpol
  • 2,177
  • 1
  • 23
  • 22
  • This has no effect in iOS 7, because the internal structure of the dot views has changed. They are no longer UIImageViews, and using bg color to hide no longer has any impact. – SimplGy Jun 14 '15 at 08:32
0

Following the previous answers I came up with the solution below. Keep in mind that I had to add valueChanged listener that calls updateDots() as well in controller to handle taps made on UIPageControl

import UIKit

class PageControl: UIPageControl {
    private struct Constants {
        static let activeColor: UIColor = .white
        static let inactiveColor: UIColor = .black
        static let locationImage: UIImage = UIImage(named: "Location")!
    }

    // Update dots when currentPage changes
    override var currentPage: Int {
        didSet {
            updateDots()
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        self.pageIndicatorTintColor = .clear
        self.currentPageIndicatorTintColor = .clear
    }

    func updateDots() {
        for (index, view) in self.subviews.enumerated() {
            // Layers will be redrawn, remove old.
            view.layer.sublayers?.removeAll()
            if index == 0 {
                drawImage(view: view)
            } else {
                drawDot(index: index, view: view)
            }
        }
    }

    private func drawDot(index: Int, view: UIView) {
        let dotLayer = CAShapeLayer()
        dotLayer.path = UIBezierPath(ovalIn: view.bounds).cgPath
        dotLayer.fillColor = getColor(index: index)

        view.layer.addSublayer(dotLayer)
    }

    private func drawImage(view: UIView) {
        let height = view.bounds.height * 2
        let width = view.bounds.width * 2
        let topMargin: CGFloat = -3.5

        let maskLayer = CALayer()
        maskLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
        maskLayer.contents = Constants.locationImage.cgImage
        maskLayer.contentsGravity = .resizeAspect

        let imageLayer = CALayer()
        imageLayer.frame = CGRect(x:0, y: topMargin, width: width, height: height)
        imageLayer.mask = maskLayer
        imageLayer.backgroundColor = getColor()

        view.backgroundColor = .clear // Otherwise black background
        view.layer.addSublayer(imageLayer)
    }

    private func getColor(index: Int? = 0) -> CGColor {
        return currentPage == index ? Constants.activeColor.cgColor : Constants.inactiveColor.cgColor
    }
}