4

When I needed to custom my UIPageControl I used this and this solution.

Slightly modifying it for the new version of swift we have :

class myPageControl: UIPageControl {
    var activeImage: UIImage!
    var inactiveImage: UIImage!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        activeImage = UIImage(named: "active.png")!
        inactiveImage = UIImage(named: "inactive.png")!
    }

    func updateDots()
    {
        for i in 0 ..< self.subviews.count
        {
            let dot : UIImageView = imageViewForSubview(self.subviews[i])
            if (i == self.currentPage) {
                dot.image = activeImage
            }
            else {
                dot.image = inactiveImage
            }
        }
    }

    func imageViewForSubview(view: UIView) -> UIImageView {
        var dot: UIImageView? = nil
        if (view.isKindOfClass(UIView)) {
            for subview: UIView in view.subviews {
                if (subview is UIImageView) {
                    dot = (subview as! UIImageView)
                }
            }
            if dot == nil {
                dot = UIImageView(frame: CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height))
                view.addSubview(dot!)
            }
        }
        else {
            dot = (view as! UIImageView)
        }
        return dot!
    }

    func setPage(page: Int) {
        super.currentPage = page
        self.updateDots()
    }
}

My problem is that I can not change the picture when you first start app. It only change when the page is changed.

In viewDidLoad(), I added setPage(0) and updateDots(), but there is no result. What could i be doing wrong?

Community
  • 1
  • 1
Mikhail Sein
  • 329
  • 3
  • 4
  • 14

4 Answers4

10

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

You will have to call "updateDots()" in viewDidAppear() and your valueChanged handler for the page control.

 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
  • 1
    Here's a better idea for calling "updateDots()". Haven't tried it out yet but Dmitry suggests overriding numberOfPages and currentPage variables --> http://stackoverflow.com/questions/12190147/customize-dot-with-image-of-uipagecontrol-at-index-0-of-uipagecontrol/41136917#41136917 – CodenameDuchess Dec 15 '16 at 15:16
  • 1
    just checked in iOS 14, self.subviews is getting only one subview. Any idea? – Soumen Sep 15 '20 at 12:17
  • 1
    iOS 14 has prefferedIndicatorImage, use that and set tintColor – OldTimes Oct 05 '20 at 12:59
  • 1
    This solution will be applicable when the active image and the inactive image are different. Otherwise, you can use "prefferedIndicatorImage" and tint color. @Laurynas Letkauskas – Soumen Oct 06 '20 at 09:16
7

I improved @CodenameDuchess's answer a bit. You can also customize the images on xibs/storyboards.

class CustomPageControl: UIPageControl {

@IBInspectable var currentPageImage: UIImage?

@IBInspectable var otherPagesImage: UIImage?

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

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

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

private func updateDots() {

    for (index, subview) in subviews.enumerated() {
        let imageView: UIImageView
        if let existingImageview = getImageView(forSubview: subview) {
            imageView = existingImageview
        } else {
            imageView = UIImageView(image: otherPagesImage)

            imageView.center = subview.center
            subview.addSubview(imageView)
            subview.clipsToBounds = false
        }
        imageView.image = currentPage == index ? currentPageImage : otherPagesImage
    }
}

private func getImageView(forSubview view: UIView) -> UIImageView? {
    if let imageView = view as? UIImageView {
        return imageView
    } else {
        let view = view.subviews.first { (view) -> Bool in
            return view is UIImageView
        } as? UIImageView

        return view
    }
}
}
Politta
  • 400
  • 4
  • 10
3

You can use UIStackView to achieve the effect you want.

Put your activeImageView and other inactiveImageViews into aStackView. Than just change the index of activeImageView in scrollViewDidScroll(_:).

func scrollViewDidScroll(_ scrollView: UIScrollView) {
  let index = Int(scrollView.contentOffset.x / scrollView.frame.width)
  aStackView.insertArrangedSubview(activeImageView, at: index)
}  

ps. UIStackView's insertArrangedSubview(_:at:) just updates the stack index of the arranged subview if it's already in the arrangedSubviews list.

a_tuo
  • 651
  • 7
  • 23
3

Apple had introduced some new APIs for UIPageControll. From iOS 14 there is an option for providing a custom image.

Here is the updated code supporting iOS 14

class CustomPageControl: UIPageControl {

@IBInspectable var currentPageImage: UIImage?

@IBInspectable var otherPagesImage: UIImage?

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

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

override func awakeFromNib() {
    super.awakeFromNib()
    if #available(iOS 14.0, *) {
        defaultConfigurationForiOS14AndAbove()
    } else {
        pageIndicatorTintColor = .clear
        currentPageIndicatorTintColor = .clear
        clipsToBounds = false
    }
}

private func defaultConfigurationForiOS14AndAbove() {
    if #available(iOS 14.0, *) {
        for index in 0..<numberOfPages {
            let image = index == currentPage ? currentPageImage : otherPagesImage
            setIndicatorImage(image, forPage: index)
        }

        // give the same color as "otherPagesImage" color.
        pageIndicatorTintColor = .gray
        
        // give the same color as "currentPageImage" color.
        currentPageIndicatorTintColor = .red 
        /*
         Note: If Tint color set to default, Indicator image is not showing. So, give the same tint color based on your Custome Image.
        */
    }
}

private func updateDots() {
    if #available(iOS 14.0, *) {
        defaultConfigurationForiOS14AndAbove()
    } else {
        for (index, subview) in subviews.enumerated() {
            let imageView: UIImageView
            if let existingImageview = getImageView(forSubview: subview) {
                imageView = existingImageview
            } else {
                imageView = UIImageView(image: otherPagesImage)
                
                imageView.center = subview.center
                subview.addSubview(imageView)
                subview.clipsToBounds = false
            }
            imageView.image = currentPage == index ? currentPageImage : otherPagesImage
        }
    }
}

private func getImageView(forSubview view: UIView) -> UIImageView? {
    if let imageView = view as? UIImageView {
        return imageView
    } else {
        let view = view.subviews.first { (view) -> Bool in
            return view is UIImageView
        } as? UIImageView

        return view
    }
}
}
Soumen
  • 2,070
  • 21
  • 25
  • 1
    in iOS 14 you should be using prefferedIndicatorImage instead – OldTimes Oct 05 '20 at 13:02
  • 1
    @Laurynas Letkauskas This solution will be applicable when the active image and the inactive image are different. Otherwise, you can use "prefferedIndicatorImage" and tint color. – Soumen Oct 06 '20 at 09:18