Reputation: 630
I want to add border color for dots in UIPageControl. Here is the small picture of it:
I am able to put second dot by configuring it from XCode but I cannot make the first and third circles' inside empty. Is there a simple way to achieve that?
Thanks :)
Upvotes: 22
Views: 23567
Reputation: 1
Using @Luke Rogers answer. I came up with solution for latest iOS versions.
-> Extend UIImage and add these functions:
extension UIImage {
/// Creates a circular outline image.
class func outlinedEllipse(size: CGSize, color: UIColor, lineWidth: CGFloat = 1.0) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setStrokeColor(color.cgColor)
context.setLineWidth(lineWidth)
// Inset the rect to account for the fact that strokes are
// centred on the bounds of the shape.
let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)
context.addEllipse(in: rect)
context.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
class func ellipse(size: CGSize, color: UIColor, lineWidth: CGFloat = 1.0) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setFillColor(color.cgColor)
context.setLineWidth(lineWidth)
// Inset the rect to account for the fact that strokes are
// centred on the bounds of the shape.
let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)
context.addEllipse(in: rect)
context.fillPath()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}}
Then Extend UIPageControl and add these functions:
extension UIPageControl {
@available(iOS 16.0, *)
func outlinedPageIndicaor(color: UIColor, outlinedColor: UIColor? = nil, count: Int) {
self.pageIndicatorTintColor =
outlinedColor != nil ? outlinedColor : color
self.currentPageIndicatorTintColor = color
let image = UIImage.outlinedEllipse(size: CGSize(width: 8.0, height: 8.0), color: .red)
let ellipse = UIImage.ellipse(size: CGSize(width: 9.0, height: 9.0), color: .red)
self.preferredIndicatorImage = image
for i in 0..<count {
self.setCurrentPageIndicatorImage(ellipse, forPage: i)
}
}
func outlinedPageIndicaor(color: UIColor, outlinedColor: UIColor? = nil, count: Int, index: Int) {
self.pageIndicatorTintColor =
outlinedColor != nil ? outlinedColor : color
self.currentPageIndicatorTintColor = color
let image = UIImage.outlinedEllipse(size: CGSize(width: 8.0, height: 8.0), color: .red)
let ellipse = UIImage.ellipse(size: CGSize(width: 9.0, height: 9.0), color: .red)
self.preferredIndicatorImage = image
for i in 0..<count {
if i != index {
self.setIndicatorImage(image, forPage: i)
} else {
self.setIndicatorImage(ellipse, forPage: index)
}
}
}}
You can use this if you are using iOS 16 or above in viewDidLoad
pageControl.outlinedPageIndicaor(color: .systemMint, count: count)
// pass same count as number of pages of pageControl
if iOS version is below 16, then use second method:
in viewDidLoad:
pageControl.outlinedPageIndicaor(color: .systemMint, count: count, index: 0)
//initial index is passed as 0 for first page
and when current page changes:
pageControl.outlinedPageIndicaor(color: .systemMint, count: count, index: index)
//pass current page in index
Upvotes: 0
Reputation: 41
Please try this custom subclassing of UIPageControl. iOS 14 allows setting indicator image with SFSymbol.
class BorderedPageControl: UIPageControl {
var selectionColor: UIColor = .black
override var currentPage: Int {
didSet {
updateBorderColor()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
currentPageIndicatorTintColor = selectionColor
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func updateBorderColor() {
if #available(iOS 14.0, *) {
let smallConfiguration = UIImage.SymbolConfiguration(pointSize: 8.0, weight: .bold)
let circleFill = UIImage(systemName: "circle.fill", withConfiguration: smallConfiguration)
let circle = UIImage(systemName: "circle", withConfiguration: smallConfiguration)
for index in 0..<numberOfPages {
if index == currentPage {
setIndicatorImage(circleFill, forPage: index)
} else {
setIndicatorImage(circle, forPage: index)
}
}
pageIndicatorTintColor = selectionColor
} else {
subviews.enumerated().forEach { index, subview in
if index != currentPage {
subview.layer.borderColor = selectionColor.cgColor
subview.layer.borderWidth = 1
} else {
subview.layer.borderWidth = 0
}
}
}
}
}
Upvotes: 2
Reputation: 5806
Modern Luke Rogers (elk_cloner says it's not modern enough for ios14)
extension UIImage
{
// https://stackoverflow.com/questions/35842040/add-border-for-dots-in-uipagecontrol
// modernized Luke Rogers
class func outlinedEllipse(size: CGSize, color: UIColor, lineWidth: CGFloat = 1.0) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
color.setFill()
context.cgContext.setLineWidth(lineWidth)
// Inset the rect to account for the fact that strokes are
// centred on the bounds of the shape.
let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)
context.cgContext.strokeEllipse(in: rect)
}
return image
}
}
Upvotes: -1
Reputation: 419
Luke Rogers's solution, written in objective-c:
-(UIImage *) outlinedEllipse:(CGSize)size color: (UIColor*) lineColor width:(CGFloat) lineWidth {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) {
return NULL;
}
CGContextSetStrokeColorWithColor(context, lineColor.CGColor);
CGContextSetLineWidth(context, lineWidth);
// Inset the rect to account for the fact that strokes are
// centred on the bounds of the shape.
CGRect rect = CGRectInset(CGRectMake(0, 0, 7.0f, 7.0f), lineWidth * 0.5, lineWidth * 0.5);
CGContextAddEllipseInRect(context, rect);
CGContextStrokePath(context);
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Upvotes: -1
Reputation: 602
Swift 5 version
func customPageControl(dotFillColor: UIColor, dotBorderColor: UIColor, dotBorderWidth: CGFloat) {
for (pageIndex, dotView) in self.subviews.enumerated() {
dotView.backgroundColor = currentPage == pageIndex ? dotFillColor : .clear
dotView.layer.cornerRadius = dotView.frame.size.height / 2
dotView.layer.borderColor = dotBorderColor.cgColor
dotView.layer.borderWidth = dotBorderWidth
}
}
Upvotes: 0
Reputation: 146
Swift 4. You can assign borderColor and then observe currentPage property to change dots' border:
class CustomPageControl: UIPageControl {
var borderColor: UIColor = .clear
override var currentPage: Int {
didSet {
updateBorderColor()
}
}
func updateBorderColor() {
subviews.enumerated().forEach { index, subview in
if index != currentPage {
subview.layer.borderColor = borderColor.cgColor
subview.layer.borderWidth = 1
} else {
subview.layer.borderWidth = 0
}
}
}
}
Upvotes: 8
Reputation: 49
Just add this two lines and add the desired image !!
pageControl.currentPageIndicatorTintColor = UIColor.init(patternImage: UIImage(named: "slider_selected")!)
pageControl.pageIndicatorTintColor = UIColor.init(patternImage: UIImage(named: "slider")!)
Upvotes: 2
Reputation: 1778
Edited- Swift 3 & 4 extension to achieve the same result-
extension UIPageControl {
func customPageControl(dotFillColor:UIColor, dotBorderColor:UIColor, dotBorderWidth:CGFloat) {
for (pageIndex, dotView) in self.subviews.enumerated() {
if self.currentPage == pageIndex {
dotView.backgroundColor = dotFillColor
dotView.layer.cornerRadius = dotView.frame.size.height / 2
}else{
dotView.backgroundColor = .clear
dotView.layer.cornerRadius = dotView.frame.size.height / 2
dotView.layer.borderColor = dotBorderColor.cgColor
dotView.layer.borderWidth = dotBorderWidth
}
}
}
}
to use it write below code in viewDidLoad() or viewDidAppear()
pageControl.customPageControl(dotFillColor: .orange, dotBorderColor: .green, dotBorderWidth: 2)
In Objective-C use below code-
- (void) customPageControlWithFillColor:(UIColor*)dotFillColor borderColor:(UIColor*)dotBorderColor borderWidth:(CGFloat)dotBorderWidth {
for (int pageIndex = 0; pageIndex < _pageControl.numberOfPages; pageIndex++) {
UIView* dotView = [_pageControl.subviews objectAtIndex:pageIndex];
if (_pageControl.currentPage == pageIndex) {
dotView.backgroundColor = dotFillColor;
dotView.layer.cornerRadius = dotView.frame.size.height / 2;
} else {
dotView.backgroundColor = [UIColor clearColor];
dotView.layer.cornerRadius = dotView.frame.size.height / 2;
dotView.layer.borderColor = dotBorderColor.CGColor;
dotView.layer.borderWidth = dotBorderWidth;
}
}
}
Output-
Upvotes: 27
Reputation: 810
Need to add this in viewDidAppear
for (int i = 0; i < _pageControl.numberOfPages; i++) {
UIView* dot = [_pageControl.subviews objectAtIndex:i];
if (i == _pageControl.currentPage) {
dot.backgroundColor = [UIColor whiteColor];
dot.layer.cornerRadius = dot.frame.size.height / 2;
} else {
dot.backgroundColor = [UIColor clearColor];
dot.layer.cornerRadius = dot.frame.size.height / 2;
dot.layer.borderColor = [UIColor whiteColor].CGColor;
dot.layer.borderWidth = 1;
}
}
Upvotes: -1
Reputation: 2418
Another approach would be to use a pattern image of the correct size (which currently is 7 points in diameter). Here's what the result looks like:
And here's how it's done:
let image = UIImage.outlinedEllipse(size: CGSize(width: 7.0, height: 7.0), color: .darkGray)
self.pageControl.pageIndicatorTintColor = UIColor.init(patternImage: image!)
self.pageControl.currentPageIndicatorTintColor = .darkGray
Which uses this simple little extension to UIImage
:
/// An extension to `UIImage` for creating images with shapes.
extension UIImage {
/// Creates a circular outline image.
class func outlinedEllipse(size: CGSize, color: UIColor, lineWidth: CGFloat = 1.0) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setStrokeColor(color.cgColor)
context.setLineWidth(lineWidth)
// Inset the rect to account for the fact that strokes are
// centred on the bounds of the shape.
let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)
context.addEllipse(in: rect)
context.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
The downside of this is that if the dot size changes in an OS update, the image will look weird as it will be tiled or clipped.
Upvotes: 20
Reputation: 2152
SWIFT 3 Version from @RiosK
func updatePageControl() {
for (index, dot) in pageControl.subviews.enumerated() {
if index == pageControl.currentPage {
dot.backgroundColor = dotColor
dot.layer.cornerRadius = dot.frame.size.height / 2;
} else {
dot.backgroundColor = UIColor.clear
dot.layer.cornerRadius = dot.frame.size.height / 2
dot.layer.borderColor = dotColor.cgColor
dot.layer.borderWidth = dotBorderWidth
}
}
}
Upvotes: 16
Reputation: 17122
I am using SMPageControl. It's a really awesome Framework written in Objective-C, so it's capable with Swift 2 and Swift 3.
The usage is completely simple:
pod 'SMPageControl'
Then in your PageViewController
:
import SMPageControl
class MyController: UIPageViewController {
var pageControl = SMPageControl()
override func viewDidLoad() {
super.viewDidLoad()
stylePageControl()
}
private func stylePageControl() {
pageControl = SMPageControl(frame: CGRect(x: 0, y: self.view.frame.size.height - 50, width: self.view.frame.size.width, height: 50))
pageControl.numberOfPages = yourPageControllerArray.count
// the first (first) picture is the item in the bar, that is unused
// the second (currentFirst) is an item that we use, when this is the current active page
// in this example, we don't have dots, but we use "pictues" as dots
let first = UIImage(named: "pageHome")?.imageWithColor(UIColor.grayColor())
let currentFirst = first?.imageWithColor(UIColor.whiteColor())
pageControl.setImage(first, forPage: 0)
pageControl.setCurrentImage(currentFirst, forPage: 0)
let second = UIImage(named: "pageMusic")?.imageWithColor(UIColor.grayColor())
let currentSecond = second?.imageWithColor(UIColor.whiteColor())
pageControl.setImage(second, forPage: 1)
pageControl.setCurrentImage(currentSecond, forPage: 1)
pageControl.indicatorMargin = 30.0 // this is the space between the dots
self.view.addSubview(pageControl)
}
UIImage Extension I've used:
extension UIImage {
func imageWithColor(color1: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color1.setFill()
let context = UIGraphicsGetCurrentContext()! as CGContextRef
CGContextTranslateCTM(context, 0, self.size.height)
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, CGBlendMode.Normal)
let rect = CGRectMake(0, 0, self.size.width, self.size.height) as CGRect
CGContextClipToMask(context, rect, self.CGImage!)
CGContextFillRect(context, rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()! as UIImage
UIGraphicsEndImageContext()
return newImage
}
}
The result looks like this:
Now we could of course use colored dots as the images, (blank colored as unused and color-filled as used) then we would have the asked result.
Upvotes: 3
Reputation: 27050
That is not possible with the current properties available for UIPageControl
. But you can do by integrating any third party page control which mimic the functionality of iOS UIPageControl
.
Other answer has applied a patch. I highly disagreed with that solution.
Upvotes: 7