I'm using a simple UIView which I want to animate and I added a gradient layer to it.
I want to increase the width of the view and the layer placed on the view,
but all I get is that the view increases its width but not the layer.
Example: Let's say I have a UIView with height = width = 50
I animate it by setting the width to: width += 50. This animation is working. If I do the same with layer then noting happens. The layer does not increase its width. I tried some things to fix this (see comments in code) but nothing is working.
Here is my code
func performNextTitleAnimation() {
let overlayViewHeight = overlayView.frame.size.height
let overlayViewWidth = overlayView.frame.size.width
let animationHeight: CGFloat = 48
let overlayViewHalfHeight = (overlayViewHeight) / 2
swipeAnimation = UIView(frame: CGRect(x: 0, y: overlayViewHalfHeight - (animationHeight/2), width: 48, height: animationHeight))
swipeAnimation.backgroundColor = .gray
swipeAnimation.layer.cornerRadius = swipeAnimation.frame.size.height / 2
gradientLayer.frame = swipeAnimation.bounds
overlayView.addSubview(swipeAnimation)
swipeAnimation.layer.addSublayer(gradientLayer)
UIView.animate(withDuration: 1.2, delay: 0, options: [.repeat], animations: {
self.gradientLayer.cornerRadius = 24
self.swipeAnimation.frame.size.width += 50
//Things I tried, but not working
//1.) self.gradientLayer.frame.size.width += 50
//2.) self.gradientLayer.frame.size.width = self.swipeAnimation.frame.size.width
//3.) self.gradientLayer.bounds = self.swipeAnimation.bounds
}, completion: nil)
}
Gradient Layer
gradientLayer.colors = [UIColor.white.cgColor, animationColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.transform = CATransform3DMakeRotation(3*(CGFloat.pi) / 2, 0, 0, 1)
Any help is highly appreciated.
The best result I've ever achieved when I needed to animate CAGrandientLayers size is to use it as a layerClass of a custom UIView:
class GrandientView: UIView {
override class var layerClass : AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
// it is safe to force cast here
// since we told UIView to use this exact type
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
// setup your gradient
}
}
Related
I tried to create some kind of timeline (with the Vector Illustrator mentality), using UIBezier and UI Label (kind of like in the calendar app) and then use UIPanGestureRecognizer to scroll it up and down. But whenever I scroll it in the simulator, it multiplies itself instead of moving like the images below (I use setNeedsDisplay as the scrollValue changes to redraw the whole mechanism). This is probably a small mistake a I did or maybe my code doesn't work.
I know I could use a UIScrollView or UITableView instead, but I tried making this as a small challenge as a custom made table because using pre-made objects feels limiting for someone like me who is used to CAD drawing or Vector Illustrator.
This image explains what happens in the Simulator:
The code I used is below:
import UIKit
class ViewController: UIViewController {
var tlobject = TimelineView()
let gesto = UIPanGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
// ===== Add TimelineView Object to view
let TLObjectFrame = CGRect(x: 0, y: 40, width: 100, height: 100)
tlobject = TimelineView(frame: TLObjectFrame)
view.addSubview(tlobject)
// ===== ADD TOUCH GESTURE =====
gesto.addTarget(self, action: #selector(touchinput))
view.addGestureRecognizer(gesto)
}
var touchStartLocation: Int = 0
var scrollDistance: Int = 0
var lastScrollDistance: Int = 0
//The following func calculates the distance scrolled/travelled by Touch gesture on the YAxis and sends the result value (scrollDistance) to the Timeline mechanism where it defines the Yposition of every UIBezier. Thanks to Mitchell Hudson on Youtube for helping me figure out how to do it on his Tutorial "06 11 touches value"
#objc func touchinput (sender: UIPanGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
touchStartLocation = Int(sender.location(in: view).y)
lastScrollDistance = scrollDistance
}
if sender.state == UIGestureRecognizer.State.changed {
let touchEndLocation = Int(sender.location(in: view).y)
let currentScrollDistance = touchEndLocation - touchStartLocation
print("deltaY", currentScrollDistance)
var newScrollDistance = lastScrollDistance + currentScrollDistance
scrollDistance = newScrollDistance
tlobject.totalScrollDistance = scrollDistance //send scrollValue to TimelineView
}
if sender.state == UIGestureRecognizer.State.ended {
print("lastScrollDistance", lastScrollDistance)
print("scroll Distance", scrollDistance)
}
}
}
//Created a new View with the TimeLine mechanism
class TimelineView: UIView {
var totalScrollDistance: Int = 0 {
didSet{
setNeedsDisplay() //this gets called everytime UIgesture position changes
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
timelinemechanism()
}
func timelinemechanism() {
let lineElements: Array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let spacing: Int = 30
let scrollDistance: Int = totalScrollDistance
let totalElements: Int = lineElements.count
for n in 1...totalElements {
//Get UILabel/UILine Yposition on screen = Array index number * the spacing + scroll distance by touch pan gesture
let yPosition = lineElements[n - 1] * spacing + scrollDistance
let linepath = UIBezierPath()
linepath.move(to: CGPoint(x: 60, y: yPosition))
linepath.addLine(to: CGPoint(x: 300, y: yPosition))
let lineshape = CAShapeLayer()
lineshape.path = linepath.cgPath
lineshape.strokeColor = UIColor.blue.cgColor
//lineshape.fillColor = UIColor.white.cgColor
//lineshape.lineWidth = 1
self.layer.addSublayer(lineshape)
let hourlabel = UILabel()
hourlabel.frame = CGRect(x: 5, y: yPosition - 20, width: 45, height: 40)
hourlabel.text = "\(n):00"
//hourlabel.font = UIFont(name: "Avenir-Claro", size: 12)
hourlabel.textColor = UIColor.blue
hourlabel.textAlignment = NSTextAlignment.right
self.addSubview(hourlabel)
}
}
}
Inside draw you only have to draw something. You add new subviews/sublayers and do not remove old ones.
Creating a new view every time you change a frame is very resource-intensive. And you don't need that, because you have the same views, you only need to change the position.
Instead, you can create your views at start and use layoutSubviews to update your views positions:
class TimelineView: UIView {
var totalScrollDistance: Int = 0 {
didSet{
setNeedsLayout() //this gets called everytime UIgesture position changes
}
}
private var lastLayoutTotalScrollDistance: Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
createTimelinemechanism()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var lineShapes = [CAShapeLayer]()
var hourLabels = [UILabel]()
override func layoutSubviews() {
super.layoutSubviews()
let offset = totalScrollDistance - lastLayoutTotalScrollDistance
lastLayoutTotalScrollDistance = totalScrollDistance
lineShapes.forEach { lineShape in
lineShape.frame.origin.y += CGFloat(offset)
}
hourLabels.forEach { hourLabel in
hourLabel.frame.origin.y += CGFloat(offset)
}
}
func createTimelinemechanism() {
let lineElements: Array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let spacing: Int = 30
let totalElements: Int = lineElements.count
for n in 1...totalElements {
//Get UILabel/UILine Yposition on screen = Array index number * the spacing + scroll distance by touch pan gesture
let yPosition = lineElements[n - 1] * spacing
let linepath = UIBezierPath()
linepath.move(to: CGPoint(x: 60, y: yPosition))
linepath.addLine(to: CGPoint(x: 300, y: yPosition))
let lineshape = CAShapeLayer()
lineshape.path = linepath.cgPath
lineshape.strokeColor = UIColor.blue.cgColor
//lineshape.fillColor = UIColor.white.cgColor
//lineshape.lineWidth = 1
// disable default layer position animation
lineshape.actions = [
"position": NSNull(),
]
self.layer.addSublayer(lineshape)
lineShapes.append(lineshape)
let hourlabel = UILabel()
hourlabel.frame = CGRect(x: 5, y: yPosition - 20, width: 45, height: 40)
hourlabel.text = "\(n):00"
//hourlabel.font = UIFont(name: "Avenir-Claro", size: 12)
hourlabel.textColor = UIColor.blue
hourlabel.textAlignment = NSTextAlignment.right
self.addSubview(hourlabel)
hourLabels.append(hourlabel)
}
}
}
More generally, you can just list all the subviews/sublayers and not keep them in separate containers.
I spent a bit more time with your question since my first thought was wrong. Let me start by saying that your approach here is not the right way to go about this. But it looks to me like you're playing with different aspects of the framework just to learn your way around and I can respect that. I spent many years working on as vector drawing program (Macromedia FreeHand) and even wrote a book about drawing with Quartz 2D back in 2006 so I understand the desire to draw it yourself.
I've reworked your example using "raw" drawing at the CGContext level. I was playing with your code in a Playground so I restructured the view creation a bit too (just so it shows up in the Playground nicely). You should be able to copy and paste this into an iOS playground and see the results.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let gesto = UIPanGestureRecognizer()
let timelineView = TimelineView()
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = true
self.view.backgroundColor = UIColor.red
self.view.bounds = CGRect(x:0, y:0, width: 320, height: 700)
// ===== Add TimelineView Object to view
view.addSubview(timelineView)
timelineView.frame = CGRect(x: 20, y: 20, width: 280, height: 660)
timelineView.backgroundColor = UIColor.white
debugPrint(timelineView.bounds)
// ===== ADD TOUCH GESTURE =====
gesto.addTarget(self, action: #selector(touchinput))
timelineView.addGestureRecognizer(gesto)
}
var touchStartLocation: Int = 0
var scrollDistance: Int = 0
var lastScrollDistance: Int = 0
//The following func calculates the distance scrolled/travelled by Touch gesture on the YAxis and sends the result value (scrollDistance) to the Timeline mechanism where it defines the Yposition of every UIBezier. Thanks to Mitchell Hudson on Youtube for helping me figure out how to do it on his Tutorial "06 11 touches value"
#objc func touchinput (sender: UIPanGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
touchStartLocation = Int(sender.location(in: view).y)
lastScrollDistance = scrollDistance
}
if sender.state == UIGestureRecognizer.State.changed {
let touchEndLocation = Int(sender.location(in: view).y)
let currentScrollDistance = touchEndLocation - touchStartLocation
print("deltaY", currentScrollDistance)
scrollDistance = lastScrollDistance + currentScrollDistance
timelineView.totalScrollDistance = scrollDistance //send scrollValue to TimelineView
}
if sender.state == UIGestureRecognizer.State.ended {
print("lastScrollDistance", lastScrollDistance)
print("scroll Distance", scrollDistance)
}
}
}
//Created a new View with the TimeLine mechanism
class TimelineView: UIView {
var totalScrollDistance: Int = 0 {
didSet {
setNeedsDisplay() //this gets called everytime UIgesture position changes
}
}
override func draw(_ rect: CGRect) {
if let cgContext = UIGraphicsGetCurrentContext() {
drawTimeline(cgContext: cgContext)
}
}
func drawTimeline(cgContext: CGContext) {
let numElements = 10
let spacing = 30
let scrollDistance = totalScrollDistance
for n in 0..<numElements {
let yPosition = n * spacing + scrollDistance
cgContext.saveGState()
cgContext.setLineWidth(1.0)
cgContext.setStrokeColor(UIColor.blue.cgColor)
cgContext.move(to: CGPoint(x: 60, y: yPosition))
cgContext.addLine(to: CGPoint(x: 300, y: yPosition))
cgContext.strokePath()
let label : NSString = "\(n):00" as NSString
label.draw(at: CGPoint(x: 5, y: yPosition - 20),
withAttributes: [.foregroundColor : UIColor.blue])
cgContext.restoreGState()
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()
The drawRect of the custom view grabs the current CGContext and passes it to the routine that does the drawing. Using something like UIBezierPath will work, of course (you saw that it did) but it has overhead (creating an actual object, copying the object into the context graphics state on each drawing, etc) that you don't necessarily need.
I'm not sure what you were doing with CAShapeLayer. You'd typically use that if you had a shape that you want to animate around the screen. I suppose you felt that, in scrolling, you might want to do that. But again this is something where you'd want to create the shape layer outside of the drawing path, keep ahold of it, manipulate it outside of the drawing path, then let the system handle worry about putting it on the screen appriopriately.
Your instincts on text are pretty good. You really don't want to handle Text drawing yourself in a system as complex as iOS. There's Unicode issues, glyph substitution, positional forms, ligatures, bi-di text... a whole host of challenges for drawing text on iOS that it's best to leave to things like UILabel. But you want to keep building your view hierarchy separate from drawing in your view hierarchy. drawRect can be called any time even a pixel of your view needs to be redrawn and adding a new subview each time is not the best way to go. In my reworked example, I'm drawing the text using NSString - It's still not the "right" way to do it but it's fairly low level while still giving the framework a chance to do some of the text handling.
In the end you would want to work with the frameworks instead of against them. You'd want to use something like UIScrollView because it will handle a thousand details (bouncing at the boundaries, ease-in/ease-out animation, touch point tracking, fast and slow scrolling, etc) but for a learning experience your code is just fine and I hope you enjoy working with iOS more!
I added the redView as a subView of the textField and used AutoLayout to constraint it to be as it appears in the image, but the problem here now is
I want to bring the red view to be on top of the border
I tried to use this method
bringSubviewToFront(floatingPlaceholderContainer)
but it didn't work, also tried to use the layer.zPosition = .greatestFiniteMagnitude and it also didn't work
Edit-
the code I used and didn't work as I expected
override func layoutSubviews() {
super.layoutSubviews()
self.redlayer.backgroundColor = UIColor.red.cgColor
self.redlayer.frame = CGRect(x: 10, y: -10, width: 100, height: 30)
self.layer.addSublayer(self.redlayer)
self.redlayer.zPosition = 10
}
and this is the result
You need to configure the timing of when you're adding this layer. This approach works fine for me:
class MyTextField : UITextField {
let redlayer = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.redlayer.backgroundColor = UIColor.red.cgColor
self.redlayer.frame = CGRect(x: 10, y: -10, width: 100, height: 30)
self.layer.addSublayer(self.redlayer)
self.redlayer.zPosition = 10
}
}
I'm currently playing with CALayers a bit. For demo purposes I'm creating a custom UIView that renders a fuel gauge. The view has two sub-layers:
one for the background
one for the hand
The layer that represents the hand is then simple rotated accordingly to point at the correct value. So far, so good. Now I want the view to resize its layers whenever the size of the view is changed. To achieve this, I created an override of the layoutSubviews method like this:
public override func layoutSubviews()
{
super.layoutSubviews()
if previousBounds == nil || !previousBounds!.equalTo(self.bounds)
{
previousBounds = self.bounds
self.updateLayers(self.bounds)
}
}
As the method is being called many times, I'm using previousBounds to make sure I only perform the update on the layers when the size has actually changed.
At first, I had just the following code in the updateLayers method to set the frames of the sub-layers:
backgroundLayer.frame = bounds.insetBy(dx: 5, dy: 5)
handLayer.frame = bounds.insetBy(dx: 5, dy: 5)
That worked fine - until the handLayer was rotated. In that case some weird things happen to its size. I suppose it is because the frame gets applied after the rotation and of course, the rotated layer doesn't actually fit the bounds and is thus resized to fit.
My current solution is to temporarily create a new CATransaction that suppresses animations, reverting the transformation back to identity, setting the frame and then re-applying the transformation like this:
CATransaction.begin()
CATransaction.setDisableActions(true)
let oldTransform = scaleLayer.transform
handLayer.transform = CATransform3DIdentity
handLayer.frame = bounds.insetBy(dx: 5, dy: 5)
handLayer.transform = oldTransform
CATransaction.commit()
I already tried omitting the CATransaction and instead applying the handLayer.affineTransform to the bounds I'm setting, but that didn't yield the expected results - maybe I did it wrong (side question: How to rotate a given CGRect around its center without doing all the maths myself)?
My question is simply: Is there a recommended was of setting the frame of a transformed layer or is the solution I found already "the" way to do it?
EDIT
Kevvv provided some sample code, which I've modified to demonstrate my problem:
class ViewController: UIViewController {
let customView = CustomView(frame: .init(origin: .init(x: 200, y: 200), size: .init(width: 200, height: 200)))
let backgroundLayer = CALayer()
let handLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(customView)
customView.backgroundColor = UIColor.blue
backgroundLayer.backgroundColor = UIColor.yellow.cgColor
backgroundLayer.frame = customView.bounds
let handPath = UIBezierPath()
handPath.move(to: backgroundLayer.position)
handPath.addLine(to: .init(x: 0, y: backgroundLayer.position.y))
handLayer.frame = customView.bounds
handLayer.path = handPath.cgPath
handLayer.strokeColor = UIColor.black.cgColor
handLayer.backgroundColor = UIColor.red.cgColor
customView.layer.addSublayer(backgroundLayer)
customView.layer.addSublayer(handLayer)
handLayer.transform = CATransform3DMakeRotation(5, 0, 0, 1)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
customView.addGestureRecognizer(tap)
}
#objc func tapped(_ sender: UITapGestureRecognizer) {
customView.frame = customView.frame.insetBy(dx:10, dy:10)
/*let animation = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
let fromValue = self.handLayer.transform
let toValue = CGFloat.pi * 2
animation.duration = 2
animation.fromValue = fromValue
animation.toValue = toValue
animation.valueFunction = CAValueFunction(name: .rotateZ)
self.handLayer.add(animation, forKey: nil)*/
}
}
class CustomView: UIView {
var previousBounds: CGRect!
override func layoutSubviews() {
super.layoutSubviews()
if previousBounds == nil || !previousBounds!.equalTo(self.bounds) {
previousBounds = self.bounds
self.updateLayers(self.bounds)
}
}
func updateLayers(_ bounds: CGRect) {
guard let sublayers = self.layer.sublayers else { return }
for sublayer in sublayers {
sublayer.frame = bounds.insetBy(dx: 5, dy: 5)
}
}
}
If you add this to a playground, then run and tap the control, you'll see what I mean. Watch the red "square".
Do you mind explaining what the "weird things happening to the size" means? I tried to replicate it, but couldn't find the unexpected effects:
class ViewController: UIViewController {
let customView = CustomView(frame: .init(origin: .init(x: 200, y: 200), size: .init(width: 200, height: 200)))
let backgroundLayer = CALayer()
let handLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(customView)
backgroundLayer.backgroundColor = UIColor.yellow.cgColor
backgroundLayer.frame = customView.bounds
let handPath = UIBezierPath()
handPath.move(to: backgroundLayer.position)
handPath.addLine(to: .init(x: 0, y: backgroundLayer.position.y))
handLayer.frame = customView.bounds
handLayer.path = handPath.cgPath
handLayer.strokeColor = UIColor.black.cgColor
customView.layer.addSublayer(backgroundLayer)
customView.layer.addSublayer(handLayer)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
customView.addGestureRecognizer(tap)
}
#objc func tapped(_ sender: UITapGestureRecognizer) {
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
let fromValue = self.handLayer.transform
let toValue = CGFloat.pi * 2
animation.duration = 2
animation.fromValue = fromValue
animation.toValue = toValue
animation.valueFunction = CAValueFunction(name: .rotateZ)
self.handLayer.add(animation, forKey: nil)
}
}
class CustomView: UIView {
var previousBounds: CGRect!
override func layoutSubviews() {
super.layoutSubviews()
if previousBounds == nil || !previousBounds!.equalTo(self.bounds) {
previousBounds = self.bounds
self.updateLayers(self.bounds)
}
}
func updateLayers(_ bounds: CGRect) {
guard let sublayers = self.layer.sublayers else { return }
for sublayer in sublayers {
sublayer.frame = bounds.insetBy(dx: 5, dy: 5)
}
}
}
Edit
I think the issue is that the red box is resized with a frame. Since a frame is always upright even if it's rotated, if you were to do an inset from a frame, it'd look like this:
However, if you were to resize the red box with bounds:
sublayer.bounds = bounds.insetBy(dx: 5, dy: 5)
sublayer.position = self.convert(self.center, from: self.superview)
instead of:
sublayer.frame = bounds.insetBy(dx: 5, dy: 5)
You'll probably have to re-center the handPath and everything else in it accordingly as well.
I want to change mask in animation
but it just like change to another mask without animation
screen record
I want it look like this
func creatMask(_ index: Int) -> CALayer {
let layer = CALayer()
let image = [#imageLiteral(resourceName: "bubble_1.png"),#imageLiteral(resourceName: "bubble_2.png"),#imageLiteral(resourceName: "bubble_3.png"),#imageLiteral(resourceName: "bubble_4.png"),#imageLiteral(resourceName: "bubble_5.png")]
layer.contents = image[index].cgImage
layer.contentsScale = image[index].scale
layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
return layer
}
#objc func transform() {
UIView.animate(withDuration: 1) {
self.redView.layer.mask = self.creatMask(1)
}
}
}
Is it possible to Increase the size of the indicator in UIPageViewController?
I have this:
And my requirement is this:
Scaling the page control will scale the dots, but will also scale the spacing in between them.
pageControl.transform = CGAffineTransform(scaleX: 2, y: 2)
If you want to keep the same spacing between dots, you'll need to transform the dots individually:
pageControl.subviews.forEach {
$0.transform = CGAffineTransform(scaleX: 2, y: 2)
}
However, if you do this in viewDidLoad, the transform has been reset by the time the view appears, so you should do this in viewDidLayoutSubviews…
override func viewDidLayoutSubviews() {
pageControl.subviews.forEach {
$0.transform = CGAffineTransform(scaleX: 2, y: 2)
}
}
You can use an UIPageControl and scale it like this :
#IBOutlet weak var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
pageControl.transform = CGAffineTransform(scaleX: 2, y: 2); //set value here
}
The problem with that is that you space between your dots will be increase too. If you want to have an accurate design with your dot you have to use 3party controls : https://www.cocoacontrols.com/
For swift 2.0 to increase or decrease size of pageControl Indicator
self.pageControl.transform = CGAffineTransformMakeScale(0.8, 0.8)
OR
self.pageControl.transform = CGAffineTransformMakeScale(1.3, 1.3)
Swift 4, 4.2 and 5
First create an outlet of page control
#IBOutlet weak var pageControl: UIPageControl!
If you want to keep the original spacing.
override func viewDidLayoutSubviews() {
pageControl.transform = CGAffineTransform(scaleX: 2, y: 2)
}
If you don't want to keep the original spacing.
override func viewDidLayoutSubviews() {
pageControl.subviews.forEach {
$0.transform = CGAffineTransform(scaleX: 2, y: 2)
}
}
Firstly, create an uiPageControl object inside inside viewDidLoad() and then set it's y position as per your requirement then apply required scale using CAAffiniteTransform as below:
var pageControl = UIPageControl()
pageControl.pageIndicatorTintColor = UIColor.gray
pageControl.currentPageIndicatorTintColor = UIColor.yellow
pageControl.transform = CGAffineTransform(scaleX: 1.3, y: 1.3) // set dot scale of pageControl
pageControl.backgroundColor = UIColor.darkGray
pageControl.numberOfPages = 3
pageControl.center = self.view.center
self.view.addSubview(pageControl) // add pageControl to view
pageControl.layer.position.y = self.view.frame.height - 100; // y position of the pageControl
Add an extension to the page controller
extension UIPageControl {
func customPageControl(dotWidth: CGFloat) {
for (pageIndex, dotView) in self.subviews.enumerated() {
dotView.frame.size = CGSize.init(width: dotWidth, height: dotWidth)
}
}
}