I need to implement a UIPageControl with custom images instead the normal dot. So I create a custom class and connect it through the storyboard.
Since ios7 the subview of UIPageControl contain UIView instead of UIImageView. The subviews of the resulting UIView(UIIpageControl subviews) doesn't contain any subviews so I receive the error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
Where I might have been wrong?
class WhitePageControl:UIPageControl{
let activeImage = UIImage(named: "dot_white")
let inactiveImage = UIImage(named: "dot_white_e")
override init(frame: CGRect){
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
func updateDots(){
println(self.subviews.count) // 3
println(self.numberOfPages) // 3
for var index = 0; index < self.subviews.count; index++ {
println(index)
var dot:UIImageView!
var dotView:UIView = self.subviews[index] as UIView
println("1")
for subview in dotView.subviews{ // NIL HERE
println("2")
if subview.isKindOfClass(UIImageView){
println("3")
dot = subview as UIImageView
if index == self.currentPage{ dot.image = activeImage }
else{ dot.image = inactiveImage }
}
}
}
}
func setCurrentPage(value:Int){
super.currentPage = value
self.updateDots()
}
}
It's my solution:
import Foundation
class PageControl: UIPageControl {
var activeImage: UIImage!
var inactiveImage: UIImage!
override var currentPage: Int {
//willSet {
didSet { //so updates will take place after page changed
self.updateDots()
}
}
convenience init(activeImage: UIImage, inactiveImage: UIImage) {
self.init()
self.activeImage = activeImage
self.inactiveImage = inactiveImage
self.pageIndicatorTintColor = UIColor.clearColor()
self.currentPageIndicatorTintColor = UIColor.clearColor()
}
func updateDots() {
for var i = 0; i < count(subviews); i++ {
var view: UIView = subviews[i] as! UIView
if count(view.subviews) == 0 {
self.addImageViewOnDotView(view, imageSize: activeImage.size)
}
var imageView: UIImageView = view.subviews.first as! UIImageView
imageView.image = self.currentPage == i ? activeImage : inactiveImage
}
}
// MARK: - Private
func addImageViewOnDotView(view: UIView, imageSize: CGSize) {
var frame = view.frame
frame.origin = CGPointZero
frame.size = imageSize
var imageView = UIImageView(frame: frame)
imageView.contentMode = UIViewContentMode.Center
view.addSubview(imageView)
}
}
change setCurrentPage to Current Page ,bcoz that one is Obj C property
func currentPage(page: Int) {
super.currentPage = page
self.updateDots()
}
try this.
Related
I am a beginner in swift
I want to customize my UIPageControl on only code
The UIPageControl style I want is:I can change the little dot above and when I switch to it he can change to another style
I found an article that provides a how-to, which can be done using
It's good, but I don't understand how it works
The method is attached for reference by friends who have the same needs
but that's not my real intention
I want to understand how this program works, can someone explain it to me? or provide a more simple and understandable method
I know so much code is because it supports versions below ios14
but I think this part may be omitted in explaining
The reason why it is not removed is to seek better and more complete code.
thank everybody
import UIKit
class CZPageControl: UIPageControl {
var currentImage: UIImage?
var inactiveImage: UIImage?
var currentTintColor: UIColor?
var inactiveTintColor: UIColor?
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
if #available(iOS 14.0, *) {
self.allowsContinuousInteraction = false
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var currentPage: Int {
didSet {
updateDots()
}
}
func updateDots() {
guard let currentImage = self.currentImage, let inactiveImage = self.inactiveImage else {
return
}
if #available(iOS 14.0, *) {
guard let dotContentView = findIndicatorContentView() else {
return
}
for (index, view) in dotContentView.subviews.enumerated() {
if view.isKind(of: UIImageView.self) {
self.currentPageIndicatorTintColor = self.currentTintColor
self.pageIndicatorTintColor = self.inactiveTintColor
let indicatorView = view as! UIImageView
indicatorView.image = nil
if index == self.currentPage {
indicatorView.image = currentImage.withRenderingMode(.alwaysTemplate)
} else {
indicatorView.image = inactiveImage.withRenderingMode(.alwaysTemplate)
}
}
}
} else {
for (index, view) in self.subviews.enumerated() {
if let dot = imageViewForSubview(view, currentPage: index) {
var size = CGSize.zero
if index == self.currentPage {
dot.tintColor = self.currentTintColor
dot.image = currentImage.withRenderingMode(.alwaysTemplate)
size = dot.image!.size
} else {
dot.tintColor = self.inactiveTintColor
dot.image = inactiveImage.withRenderingMode(.alwaysTemplate)
size = dot.image!.size
}
if let superview = dot.superview {
let x = (superview.frame.size.width - size.width) / 2.0
let y = (superview.frame.size.height - size.height) / 2.0
dot.frame = CGRect(x: x, y: y, width: size.width, height: size.height)
}
}
}
}
}
// iOS 14之前创建UIImageView使用
func imageViewForSubview(_ view: UIView, currentPage: Int) -> UIImageView? {
var dot: UIImageView?
if view.isKind(of: UIView.self) {
for subview in view.subviews {
if subview.isKind(of: UIImageView.self) {
dot = (subview as! UIImageView)
break
}
}
if dot == nil {
dot = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
view.addSubview(dot!)
}
} else {
dot = (view as! UIImageView)
}
return dot
}
#available(iOS 14.0, *)
func findIndicatorContentView() -> UIView? {
for contentView in self.subviews {
if let contentViewClass = NSClassFromString("_UIPageControlContentView"), contentView.isKind(of: contentViewClass) {
for indicatorContentView in contentView.subviews {
if let indicatorContentViewClass = NSClassFromString("_UIPageControlIndicatorContentView"), indicatorContentView.isKind(of: indicatorContentViewClass) {
return indicatorContentView
}
}
}
}
return nil
}
}
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
}
}
I created a custom UIView that I instantiate with an object ConnectDetailItem.
Code of my custom view :
class InfosConnectView: UIView {
var view: UIView!
#IBOutlet weak var categorie: UILabel!
#IBOutlet weak var distance: UILabel!
#IBOutlet weak var followers: UILabel!
#IBOutlet weak var descriptionTextView: UITextView!
var connectDetailsItem:ConnectDetailsItem!
convenience init(connectDetailsItem:ConnectDetailsItem, frame:CGRect) {
self.init(frame: frame)
self.connectDetailsItem = connectDetailsItem
xibSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
func xibSetup() {
view = loadViewFromNib()
// use bounds not frame or it'll be offset
view.frame = bounds
// Make the view stretch with containing view
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
view.layer.borderWidth = 1
view.layer.borderColor = UIColor(hex: "#DDDDDD").cgColor
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "viewInfosConnect", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
override func awakeFromNib() {
super.awakeFromNib()
//Infos Connect
self.categorie.text = "\(self.connectDetailsItem.category)"
self.categorie.text = "\(self.connectDetailsItem.category)"
if (self.connectDetailsItem.distance < 1000) {
self.distance.text = "\(self.connectDetailsItem.distance) m"
} else {
let distance:NSString = NSString(format: "%.01f", Float(self.connectDetailsItem.distance)/1000)
self.distance.text = "\(distance) km"
}
if(self.connectDetailsItem.followCount < 2) {
if(self.connectDetailsItem.followCount < 1) {
self.followers.text = "0 abonné"
} else {
self.followers.text = "\(self.connectDetailsItem.followCount) abonné"
}
} else {
self.followers.text = "\(self.connectDetailsItem.followCount) abonnés"
}
self.descriptionTextView.text = self.connectDetailsItem.description
}
}
In awakeFromNib(), connectDetailItem is nil. Why ?
I instanciate my view like this : let viewInfos = InfosConnectView(connectDetailsItem: self.connectDetailsItem, frame: CGRect(x: 0, y: 9, width: self.view.frame.width, height: 200))
I set breakpoints and before entering the awakeFromNib function connectDetailItem is not nil.
I have found the solution.
I put the code in the function loadViewFromNib like this :
func loadViewFromNib() -> InfosConnectView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "viewInfosConnect", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! InfosConnectView
//Infos Connect
view.categorie.text = "\(self.connectDetailsItem.category)"
if (self.connectDetailsItem.distance < 1000) {
view.distance.text = "\(self.connectDetailsItem.distance) m"
} else {
let distance:NSString = NSString(format: "%.01f", Float(self.connectDetailsItem.distance)/1000)
view.distance.text = "\(distance) km"
}
if(self.connectDetailsItem.followCount < 2) {
if(self.connectDetailsItem.followCount < 1) {
view.followers.text = "0 abonné"
} else {
view.followers.text = "\(self.connectDetailsItem.followCount) abonné"
}
} else {
view.followers.text = "\(self.connectDetailsItem.followCount) abonnés"
}
view.descriptionTextView.text = self.connectDetailsItem.description
return view
}
I'm new in ios swift and want to change UIPageControl dots size via images.
I found same issue here (UIPageControl custom class - found nil changing image to the dots)
but seems the answer is wrong, still in page control view subviews(UIImageView) is nil
As I don't have enough reputation have to ask here.
here how I'm adding image views to the scroll view
let imgOne = UIImageView(frame: CGRectMake(0, 0,scrollViewWidth, scrollViewHeight))
imgOne.image = UIImage(named: "1")
let imgTwo = UIImageView(frame: CGRectMake(scrollViewWidth, 0,scrollViewWidth, scrollViewHeight))
imgTwo.image = UIImage(named: "2")
let imgThree = UIImageView(frame: CGRectMake(scrollViewWidth*2, 0,scrollViewWidth, scrollViewHeight))
imgThree.image = UIImage(named: "3")
let imgFour = UIImageView(frame: CGRectMake(scrollViewWidth*3, 0,scrollViewWidth, scrollViewHeight))
imgFour.image = UIImage(named: "4")
self.scrollView.addSubview(imgOne)
self.scrollView.addSubview(imgTwo)
self.scrollView.addSubview(imgThree)
self.scrollView.addSubview(imgFour)
and below the answer to the problem
class PageControl: UIPageControl {
var activeImage: UIImage!
var inactiveImage: UIImage!
override var currentPage: Int {
willSet {
self.updateDots()
}
}
convenience init(activeImage: UIImage, inactiveImage: UIImage) {
self.init()
self.activeImage = activeImage
self.inactiveImage = inactiveImage
self.pageIndicatorTintColor = UIColor.clearColor()
self.currentPageIndicatorTintColor = UIColor.clearColor()
}
func updateDots() {
for var i = 0; i < count(subviews); i++ {
var view: UIView = subviews[i] as! UIView
if count(view.subviews) == 0 {
self.addImageViewOnDotView(view, imageSize: activeImage.size)
}
var imageView: UIImageView = view.subviews.first as! UIImageView // nil
imageView.image = self.currentPage == i ? activeImage : inactiveImage
}
}
// MARK: - Private
func addImageViewOnDotView(view: UIView, imageSize: CGSize) {
var frame = view.frame
frame.origin = CGPointZero
frame.size = imageSize
var imageView = UIImageView(frame: frame)
imageView.contentMode = UIViewContentMode.Center
view.addSubview(imageView)
}
}
Spending few days I solved it in the following way...
Don't know how is good the solution, feel free to to improve...
There was 2 problems
1: in function updateDots, should look like this
func updateDots() {
for var i = 0; i < subviews.count; i++ {
var imageView: UIImageView? = nil
let view: UIView = subviews[i]
if (view.subviews.first is UIImageView) {
if (subviews.count == 0) {
addImageViewOnDotView(view, imageSize: activeImage.size)
}
imageView = (view.subviews.first as! UIImageView)
imageView!.image = self.currentPage == i ? activeImage : inactiveImage
}
if (imageView == nil) {
imageView = UIImageView(frame: CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height));
imageView!.image = self.currentPage == i ? activeImage : inactiveImage
view.addSubview(imageView!)
}
}
}
2: When call this function, should be after (didSet) scroll not before (willSet)
override var currentPage: Int {
didSet {
self.updateDots()
}
}
I spent days to try to solve this problem but can't find any solution (except programmaticaly):
I have 2 UIViewController where the 2nd is a UIChildViewController.
In the ChildViewController I have an IBOutlet UIView class's attribute linked to a CustomClassUIview in the storyboard.
This CustomClassUIview have methods and attribute to update the shape layer define inside this class.
The problem is when I try to access to one attribute of this custom uiview, it returns nil.
I know that the IBOutlet is fired outside viewDidLoad, viewWillAppear,... but I don't know how to keep alloc.
I did with storyboard to be easier to design but if I did this only programmaticaly it works.
Any help please.
class ChildViewController: UIViewController {
#IBOutlet weak var customUIView: CustomClassUIView!
var upperValueProgress:CGFloat = 0 {
didSet {
self.customUIView.progress = upperValueProgress
updateLayerFrames()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.greenColor()
}
override func viewWillAppear(animated: Bool) {
customUIView.progress = 0
}
func updateLayerFrames() {
customUIView.reveal()
}
}
class FirstViewController: UIViewController {
var customUIViewController:ChildViewController!
override func viewDidLoad() {
super.viewDidLoad()
self.customUIViewController = ChildViewController()
}
...
func update(){
self.customUIViewController.upperValueProgress = 56 // Example
}
,;
#IBDesignable class CustomClassUIview: UIView {
let pathLayer = CAShapeLayer()
var circleRadius: CGFloat {
get {
return self.frame.width / 2
}
set {
}
}
#IBInspectable var progress: CGFloat = 0 {
didSet {
reveal()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
reveal()
}
func configure() {
pathLayer.frame = self.bounds
pathLayer.lineWidth = 2
pathLayer.backgroundColor = UIColor.clearColor().CGColor
pathLayer.fillColor = UIColor.clearColor().CGColor
pathLayer.strokeColor = UIColor.redColor().CGColor
layer.addSublayer(segmentTimerPathLayer)
backgroundColor = UIColor.whiteColor()
progress = 0
}
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: self.bounds.minX, y: self.bounds.minY, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = 0
circleFrame.origin.y = 0
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalInRect: circleFrame())
}
override func layoutSubviews() {
super.layoutSubviews()
pathLayer.frame = bounds
pathLayer.path = circlePath().CGPath
}
func reveal() {
println(progress)
}
}
I already created a custom control for UISlider. This UISlider have 2 thumbs so it can find minimum and maximum values. I take these tutorials as my references.
http://www.raywenderlich.com/76433/how-to-make-a-custom-control-swift
http://www.sitepoint.com/wicked-ios-range-slider-part-one/
Compiled successfully but my UISlider looks weird. You can take a look my github repository here.
https://github.com/datomnurdin/RevivalxSwiftSlider
My custom control for UISlider
import UIKit
class RangeSlider: UIControl {
var minimumValue: Float!
var maximumValue: Float!
var minimumRange: Float!
var selectedMinimumValue: Float!
var selectedMaximumValue: Float!
var distanceFromCenter: Float!
var _padding: Float!
var _maxThumbOn: Bool!
var _minThumbOn: Bool!
var _minThumb = UIImageView()
var _maxThumb = UIImageView()
var _track = UIImageView()
var _trackBackground = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
_minThumbOn = false;
_maxThumbOn = false;
_padding = 20;
let _trackBackground = UIImageView(image: UIImage(named: "bar-background.png"))
_trackBackground.center = self.center;
self.addSubview(_trackBackground)
let _track = UIImageView(image: UIImage(named: "bar-highlight.png"))
_track.center = self.center
self.addSubview(_track)
let _minThumb = UIImageView(image: UIImage(named: "handle.png"), highlightedImage: UIImage(named: "handle-hover.png"))
_minThumb.frame = CGRectMake(0,0, self.frame.size.height,self.frame.size.height);
_minThumb.contentMode = UIViewContentMode.Center
self.addSubview(_minThumb)
let _maxThumb = UIImageView(image: UIImage(named: "handle.png"), highlightedImage: UIImage(named: "handle-hover.png"))
_maxThumb.frame = CGRectMake(0,0, self.frame.size.height,self.frame.size.height)
_maxThumb.contentMode = UIViewContentMode.Center
self.addSubview(_maxThumb)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
_minThumb.center = CGPointMake(CGFloat(self.xForValue(selectedMinimumValue)), self.center.y);
_maxThumb.center = CGPointMake(CGFloat(self.xForValue(selectedMaximumValue)), self.center.y)
NSLog("Tapable size %f", _minThumb.bounds.size.width);
self.updateTrackHighlight()
}
func xForValue(value: Float) -> Float {
return (Float(self.frame.size.width) - (_padding * 2)) * ((value - minimumValue) / (maximumValue - minimumValue)) + _padding
}
func valueForX(x: Float) -> Float {
return minimumValue + (x - _padding) / (Float(self.frame.size.width) - (_padding * 2)) * (maximumValue - minimumValue)
}
func continueTrackingWithTouch(touch: UITouch, event: UIEvent) -> Bool {
if !_minThumbOn && !_maxThumbOn {
return true
}
var touchPoint: CGPoint = touch.locationInView(self)
if (_minThumbOn != nil) {
_minThumb.center = CGPointMake(max(CGFloat(self.xForValue(minimumValue)),min(touchPoint.x - CGFloat(distanceFromCenter), CGFloat(self.xForValue(selectedMaximumValue - minimumRange)))),CGFloat(_minThumb.center.y))
selectedMinimumValue = self.valueForX(Float(_minThumb.center.x))
}
if (_maxThumbOn != nil) {
_maxThumb.center = CGPointMake(min(CGFloat(self.xForValue(maximumValue)), max(touchPoint.x - CGFloat(distanceFromCenter), CGFloat(self.xForValue(selectedMinimumValue + minimumRange)))), CGFloat(_maxThumb.center.y))
selectedMaximumValue = self.valueForX(Float(_maxThumb.center.x))
}
self.updateTrackHighlight()
self.setNeedsLayout()
self.sendActionsForControlEvents(UIControlEvents.ValueChanged)
return true
}
func beginTrackingWithTouch(touch: UITouch, event: UIEvent) -> Bool {
var touchPoint: CGPoint = touch.locationInView(self)
if(CGRectContainsPoint(_minThumb.frame, touchPoint)){
_minThumbOn = true;
distanceFromCenter = Float(touchPoint.x - _minThumb.center.x)
}
else if(CGRectContainsPoint(_maxThumb.frame, touchPoint)){
_maxThumbOn = true;
distanceFromCenter = Float(touchPoint.x - _maxThumb.center.x)
}
return true;
}
func endTrackingWithTouch(touch: UITouch, event: UIEvent) {
_minThumbOn = false;
_maxThumbOn = false;
}
func updateTrackHighlight() {
_track.frame = CGRectMake(
_minThumb.center.x,
_track.center.y - (_track.frame.size.height/2),
_maxThumb.center.x - _minThumb.center.x,
_track.frame.size.height
);
}
}
My output
It suppose to be like this
How can I fix this?
Your problem lies at the 'let'.
_minThumb = UIImageView(image: UIImage(named: "handle.png"), highlightedImage: UIImage(named: "handle-hover.png"))
_minThumb.frame = CGRectMake(0,0, self.frame.size.height,self.frame.size.height);
_minThumb.contentMode = UIViewContentMode.Center
self.addSubview(_minThumb)
_maxThumb = UIImageView(image: UIImage(named: "handle.png"), highlightedImage: UIImage(named: "handle-hover.png"))
_maxThumb.frame = CGRectMake(0,0, self.frame.size.height,self.frame.size.height)
_maxThumb.contentMode = UIViewContentMode.Center
self.addSubview(_maxThumb)
Just remove the 'let' and it should work, this is because you are using _minThumb and _maxThumb globally, but you declared both as a constant, so they would not respond to any changes once initalized.