Swift iOS -How to Achieve Multi line SegmentedControl with different Font Sizes - ios

I have SegmentedControl with 2 lines using:
// AppDelegate
UILabel.appearanceWhenContainedInInstancesOfClasses([UISegmentedControl.self]).numberOfLines = 0
The problem is the line fonts are the same exact size. I need to change the titleTextAttributes for each line so that the second line is smaller then the first line.
I know I can use this for both lines:
segmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 17))
How can I do this?
// The SegmentedControl
let segmentedControl: UISegmentedControl = {
let segmentedControl = UISegmentedControl(items: ["Pizza\n123.1K", "Turkey Burgers\n456.2M", "Gingerale\n789.3B"])
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
segmentedControl.tintColor = UIColor.orange
segmentedControl.backgroundColor = .white
segmentedControl.isHighlighted = true
segmentedControl.addTarget(self, action: #selector(selectedIndex(_:)), for: .valueChanged)
return segmentedControl
}()

You'll want to create a custom control by subclassing UIControl. Here's a quick example:
CustomSegmentedControl.swift
import UIKit
import CoreImage
public class CustomSegmentedControl: UIControl {
public var borderWidth: CGFloat = 1.0
public var selectedSegementIndex = 0 {
didSet {
self.styleButtons()
}
}
public var numberOfSegments: Int {
return self.segments.count
}
private var buttons: [UIButton] = []
private var stackView = UIStackView(frame: CGRect.zero)
private var stackBackground = UIView(frame: CGRect.zero)
private var segments: [NSAttributedString] = [] {
didSet {
for subview in self.stackView.arrangedSubviews {
subview.removeFromSuperview()
}
self.buttons = []
for i in 0..<segments.count {
let segment = segments[i]
self.createAndAddSegmentButton(title: segment)
}
self.styleButtons()
}
}
override public init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
private func setup() {
self.addSubview(stackBackground)
self.stackBackground.constrainToBounds(of: self)
self.addSubview(stackView)
self.stackView.constrainToBounds(of: self)
self.stackView.axis = .horizontal
self.stackView.distribution = .fillEqually
self.stackView.spacing = borderWidth
self.layer.cornerRadius = 5.0
self.layer.borderWidth = borderWidth
self.clipsToBounds = true
self.stackBackground.backgroundColor = tintColor
}
private func createAndAddSegmentButton(title: NSAttributedString) {
let button = createSegmentButton(title: title)
self.buttons.append(button)
self.stackView.addArrangedSubview(button)
}
private func createSegmentButton(title: NSAttributedString) -> UIButton {
let button = UIButton(frame: CGRect.zero)
button.titleLabel?.numberOfLines = 0
button.titleLabel?.textAlignment = .center
button.setAttributedTitle(title, for: .normal)
button.addTarget(self, action: #selector(self.actSelected(button:)), for: .touchUpInside)
return button
}
override public var tintColor: UIColor! {
willSet {
self.layer.borderColor = newValue.cgColor
self.stackBackground.backgroundColor = newValue
}
}
public func setSegments(_ segments: [NSAttributedString]) {
self.segments = segments
}
#objc private func actSelected(button: UIButton) {
guard let index = self.buttons.index(of: button) else {
print("invalid selection should never happen, would want to handle better than this")
return
}
self.selectedSegementIndex = index
self.sendActions(for: .valueChanged)
}
private func styleButtons() {
for i in 0..<self.buttons.count {
let button = self.buttons[i]
if i == selectedSegementIndex {
button.backgroundColor = self.tintColor
button.titleLabel?.textColor = self.backgroundColor ?? .white
} else {
button.backgroundColor = self.backgroundColor
button.titleLabel?.textColor = self.tintColor
}
}
}
}
extension UIView {
func constrainToBounds(of view: UIView) {
self.translatesAutoresizingMaskIntoConstraints = false
let attrs: [NSLayoutAttribute] = [.leading, .top, .trailing, .bottom]
let constraints = attrs.map { (attr) -> NSLayoutConstraint in
return NSLayoutConstraint(item: self,
attribute: attr,
relatedBy: .equal,
toItem: view,
attribute: attr,
multiplier: 1.0,
constant: 0)
}
NSLayoutConstraint.activate(constraints)
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var customSegment: CustomSegmentedControl!
private var segments: [NSAttributedString] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.customSegment.backgroundColor = .white
self.customSegment.tintColor = .orange
let pizza = createText(title: "Pizza", subTitle: "123K")
let turkey = createText(title: "Turkey Burgers", subTitle: "456.2M")
let gingerAle = createText(title: "Gingerale", subTitle: "789.3B")
self.segments = [pizza, turkey, gingerAle]
self.customSegment.setSegments(self.segments)
self.customSegment.addTarget(self, action: #selector(self.segmentSelectionChanged(control:)), for: .valueChanged)
}
#objc private func segmentSelectionChanged(control: CustomSegmentedControl) {
let segment = self.segments[control.selectedSegementIndex]
print("selected segment = \(segment.string)")
}
func createText(title: String, subTitle: String) -> NSAttributedString {
let titleStr = NSMutableAttributedString(string: "\(title)\n", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)])
let subStr = NSAttributedString(string: subTitle, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 10)])
titleStr.append(subStr)
return titleStr
}
}

Related

Designable Button does not show images from assets folder

I customized a button and I wanted to make it designable in the storyboard.
If I set the background to an image which is stored in the assets folder, the storyboard doesn't show anything. I still get a transparent button. Any ideas how i can fix this?
import UIKit
#IBDesignable class AnswerButton: UIButton {
private static let BTN_NORMAL_IMAGE_NAME = "ButtonNormal"
private static let BTN_ANSWERED_IMAGE_NAME = "ButtonAnswered"
enum AnswerState : Int {
case normal = 0
case answered
case correctAnswered
case wrongAnswered
}
var answerState: AnswerState = .normal {
didSet {
if answerState == .normal {
self.answerLabel.textColor = UIColor.white
self.setBackgroundImage(UIImage(named: AnswerButton.BTN_NORMAL_IMAGE_NAME), for: .normal)
} else if answerState == .answered {
self.answerLabel.textColor = UIColor.black
self.setBackgroundImage(UIImage(named: AnswerButton.BTN_ANSWERED_IMAGE_NAME), for: .normal)
} else if answerState == .correctAnswered {
self.answerLabel.textColor = UIColor.blue
self.setBackgroundImage(UIImage(named: AnswerButton.BTN_ANSWERED_IMAGE_NAME), for: .normal)
}
}
}
#IBInspectable var myAnswerState: Int {
get {
return self.answerState.rawValue
}
set (value) {
self.setAnswerState(AnswerState(rawValue: value) ?? .normal)
}
}
#IBInspectable var letter: String = "" {
didSet {
self.letterLabel.text = String(letter.first!) + ":"
}
}
#IBInspectable var answer: String = "" {
didSet {
self.answerLabel.text = answer
}
}
public let letterLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.orange
label.font = UIFont(name: "Arial Rounded MT Bold", size: 24)
return label
}()
public let answerLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.white
label.font = UIFont(name: "Arial Rounded MT Bold", size: 22)
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
self.letterLabel.frame = CGRect(x: 40, y: 0, width: 30, height: self.frame.height)
self.answerLabel.frame = CGRect(x: 80, y: 0, width: 310, height: self.frame.height)
}
private func setAnswerState(_ state: AnswerState) {
self.answerState = state
}
private func setLetter(_ letter: String) {
self.letter = letter
}
private func setAnswer(_ answer: String) {
self.answer = answer
}
init(letter: String, selectionState: AnswerState, answer: String) {
super.init(frame: CGRect(x: 0, y: 0, width: 420, height: 47))
self.sharedInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.sharedInit()
}
#objc
func touchUpInside(sender: UIButton) {
self.setAnswerState(.answered)
}
func sharedInit() {
self.adjustsImageWhenHighlighted = false
self.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
self.setLetter(letter)
self.setAnswer(answer)
self.setAnswerState(.normal)
self.addSubview(self.letterLabel)
self.addSubview(self.answerLabel)
}
override func prepareForInterfaceBuilder() {
self.sharedInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.sharedInit()
}
}
an image with the name ButtonBackground lives in the assets folder.
You have a few issues in your code...
First, to get images to load at design-time / #IBDesignable, you need to tell Interface Builder where to get them:
let dynamicBundle = Bundle(for: AnswerButton.self)
let img = UIImage(named: AnswerButton.BTN_NORMAL_IMAGE_NAME, in: dynamicBundle, compatibleWith: nil)
Unless you're doing something out-of-the-ordinary with your bundle structures, you can use that both at design-time and run-time. That is, you don't conditional code.
Next, in your #IBInspectable var letter / didSet:
#IBInspectable var letter: String = "" {
didSet {
self.letterLabel.text = String(letter.first!) + ":"
}
}
make sure your string is not empty or it will crash:
#IBInspectable var letter: String = "" {
didSet {
if !letter.isEmpty {
self.letterLabel.text = String(letter.first!) + ":"
}
}
}
Next, your sharedInit() func will be called when you change an #IBInspectable value, so don't call this:
self.setAnswerState(.normal)
or your button will never reflect any other value.
And, the way you're setting frames for your labels looks problematic -- if the button width is less than 390-pts, the answerLabel won't fit.
Lastly, I'd suggest loading your two images at init, instead of re-loading every time the state changes.
Here's an update to your class:
#IBDesignable class AnswerButton: UIButton {
private static let BTN_NORMAL_IMAGE_NAME = "ButtonNormal"
private static let BTN_ANSWERED_IMAGE_NAME = "ButtonAnswered"
private var normalImage: UIImage!
private var answeredImage: UIImage!
enum AnswerState : Int {
case normal
case answered
case correctAnswered
case wrongAnswered
}
var answerState: AnswerState = .normal {
didSet {
switch answerState {
case .answered:
self.answerLabel.textColor = UIColor.black
self.setBackgroundImage(answeredImage, for: .normal)
case .correctAnswered:
self.answerLabel.textColor = UIColor.blue
self.setBackgroundImage(answeredImage, for: .normal)
case .wrongAnswered:
self.answerLabel.textColor = UIColor.red
self.setBackgroundImage(answeredImage, for: .normal)
default:
self.answerLabel.textColor = UIColor.white
self.setBackgroundImage(normalImage, for: .normal)
}
}
}
#IBInspectable var myAnswerState: Int {
get {
return self.answerState.rawValue
}
set (value) {
if let t: AnswerState = AnswerState(rawValue: value) {
self.setAnswerState(t)
} else {
self.setAnswerState(.normal)
}
}
}
#IBInspectable var letter: String = "" {
didSet {
if !letter.isEmpty {
self.letterLabel.text = String(letter.first!) + ":"
}
}
}
#IBInspectable var answer: String = "" {
didSet {
self.answerLabel.text = answer
}
}
public let letterLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.orange
label.font = UIFont(name: "Arial Rounded MT Bold", size: 24)
label.backgroundColor = UIColor.red.withAlphaComponent(0.25)
return label
}()
public let answerLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.white
label.font = UIFont(name: "Arial Rounded MT Bold", size: 22)
label.backgroundColor = UIColor.green.withAlphaComponent(0.25)
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
self.letterLabel.frame = CGRect(x: 40, y: 0, width: 30, height: self.frame.height)
self.answerLabel.frame = CGRect(x: 80, y: 0, width: 310, height: self.frame.height)
}
private func setAnswerState(_ state: AnswerState) {
self.answerState = state
}
private func setLetter(_ letter: String) {
self.letter = letter
}
private func setAnswer(_ answer: String) {
self.answer = answer
}
init(letter: String, selectionState: AnswerState, answer: String) {
super.init(frame: CGRect(x: 0, y: 0, width: 420, height: 47))
setAnswer(answer)
self.sharedInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.sharedInit()
}
#objc
func touchUpInside(sender: UIButton) {
self.setAnswerState(.answered)
}
func sharedInit() {
self.adjustsImageWhenHighlighted = false
self.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
self.setLetter(letter)
self.setAnswer(answer)
// don't call this here!
//self.setAnswerState(.normal)
self.addSubview(self.letterLabel)
self.addSubview(self.answerLabel)
// load normal / answered images once
let dynamicBundle = Bundle(for: AnswerButton.self)
if let img = UIImage(named: AnswerButton.BTN_NORMAL_IMAGE_NAME, in: dynamicBundle, compatibleWith: nil) {
self.normalImage = img
}
if let img = UIImage(named: AnswerButton.BTN_ANSWERED_IMAGE_NAME, in: dynamicBundle, compatibleWith: nil) {
self.answeredImage = img
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.sharedInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.sharedInit()
}
}

How to get info which specific button is tapped in UIView in another view

I want to add a countdown option to my application. There are 4 ways to select the countdown time. I made a separate UIView which I named BreakTimeView. I have 4 buttons there. I want to get info in the main viewController when pressing one of them. I want this info to give me a specific number of seconds for the timer to count down. I know that if I made this view from scratch in the main viewController, I could trigger my own action. But I don't know how to do it to pass this number of seconds (Int) if the button is in a separate view. I did something like that, but in my opinion it doesn't look good and the question is how can I do it better / smarter?
class ViewController: UIViewController {
var value: Int = 0
var bV = BreakTimeView()
override func viewDidLoad() {
super.viewDidLoad()
bV = BreakTimeView(frame: CGRect(x: 0,
y: view.frame.maxY + 50,
width: view.frame.size.width,
height: view.frame.size.width))
view.addSubview(bV)
let tap = UITapGestureRecognizer(target: self, action: #selector(animate))
view.addGestureRecognizer(tap)
bV.checkButtonPressed = {
self.value = 30
print(self.value)
}
bV.checkButtonPressed2 = {
self.value = 45
print(self.value)
}
bV.checkButtonPressed3 = {
self.value = 60
print(self.value)
}
bV.checkButtonPressed4 = {
self.value = 90
print(self.value)
}
}
#objc func animate(){
UIView.animate(withDuration: 1) {
self.bV.transform = CGAffineTransform(translationX: 0, y: -self.bV.frame.size.width - 50)
} completion: { completed in
print("Ekran wjechał")
}
}
}
class BreakTimeView: UIView{
private let title = UILabel()
private let button1 = UIButton()
private let button2 = UIButton()
private let button3 = UIButton()
private let button4 = UIButton()
private let horizonralStack1 = UIStackView()
private let horizontalStack2 = UIStackView()
private let verticalStack = UIStackView()
var checkButtonPressed : (() -> ()) = {}
var checkButtonPressed2 : (() -> ()) = {}
var checkButtonPressed3 : (() -> ()) = {}
var checkButtonPressed4 : (() -> ()) = {}
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 20
backgroundColor = .gray
configureTitile()
configureButtons()
configureHS1()
configureHS2()
addSubview(verticalStack)
verticalStack.addArrangedSubview(horizonralStack1)
verticalStack.addArrangedSubview(horizontalStack2)
verticalStack.axis = .vertical
verticalStack.distribution = .fillEqually
verticalStack.spacing = 20
verticalStack.translatesAutoresizingMaskIntoConstraints = false
verticalStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
verticalStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
verticalStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 20).isActive = true
verticalStack.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureTitile() {
title.text = "Wybierz czas przerwy"
title.font = UIFont.systemFont(ofSize: 25)
title.textAlignment = .center
title.layer.cornerRadius = 20
title.layer.masksToBounds = true
title.backgroundColor = .red
title.translatesAutoresizingMaskIntoConstraints = false
addSubview(title)
title.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 40).isActive = true
title.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -40).isActive = true
title.topAnchor.constraint(equalTo: self.topAnchor, constant: -40).isActive = true
title.heightAnchor.constraint(equalToConstant: 80).isActive = true
}
private func configureButtons(){
button1.layer.cornerRadius = 20
button1.backgroundColor = .red
button1.setTitle("30s", for: .normal)
button1.titleLabel?.font = UIFont.systemFont(ofSize: 30)
button1.addTarget(self, action: #selector(butt1), for: .touchUpInside)
button2.layer.cornerRadius = 20
button2.backgroundColor = .red
button2.setTitle("45s", for: .normal)
button2.titleLabel?.font = UIFont.systemFont(ofSize: 30)
button2.addTarget(self, action: #selector(butt2), for: .touchUpInside)
button3.layer.cornerRadius = 20
button3.backgroundColor = .red
button3.setTitle("60s", for: .normal)
button3.titleLabel?.font = UIFont.systemFont(ofSize: 30)
button3.addTarget(self, action: #selector(butt3), for: .touchUpInside)
button4.layer.cornerRadius = 20
button4.backgroundColor = .red
button4.setTitle("90s", for: .normal)
button4.titleLabel?.font = UIFont.systemFont(ofSize: 30)
button4.addTarget(self, action: #selector(butt4), for: .touchUpInside)
}
#objc private func butt1(){
checkButtonPressed()
}
#objc private func butt2(){
checkButtonPressed2()
}
#objc private func butt3(){
checkButtonPressed3()
}
#objc private func butt4(){
checkButtonPressed4()
}
fileprivate func configureHS1() {
horizonralStack1.addArrangedSubview(button1)
horizonralStack1.addArrangedSubview(button2)
horizonralStack1.axis = .horizontal
horizonralStack1.distribution = .fillEqually
horizonralStack1.spacing = 20
}
fileprivate func configureHS2() {
horizontalStack2.addArrangedSubview(button3)
horizontalStack2.addArrangedSubview(button4)
horizontalStack2.axis = .horizontal
horizontalStack2.distribution = .fillEqually
horizontalStack2.spacing = 20
}
Of course, I will not print the number of seconds in the application, but I will pass it to a separate function that supports the timer, but for the purposes of the demo I only have this
Inside BreakTimeView replace these callbacks -
var checkButtonPressed : (() -> ()) = {}
var checkButtonPressed2 : (() -> ()) = {}
var checkButtonPressed3 : (() -> ()) = {}
var checkButtonPressed4 : (() -> ()) = {}
with
var checkButtonPressed : ((_ numberOfSeconds: Int) -> Void) = {}
Now your button actions would look like this.
#objc private func action1() {
checkButtonPressed(30)
}
#objc private func action2() {
checkButtonPressed(45)
}
#objc private func action3() {
checkButtonPressed(60)
}
#objc private func action4() {
checkButtonPressed(90)
}
At the call site, it would look like this -
bV.checkButtonPressed = { [weak self] (numberOfSeconds) in
self?.value = numberOfSeconds
print(numberOfSeconds)
}

UITextfield isBecomeFirstResponder not working in #IBDesignable of UIView

I am trying to make OTP Pin view and created everything and works well except textfield not moving automatically.
class OTPTextField: UITextField {
var previousTextField: UITextField?
var nextTextFiled: UITextField?
override func deleteBackward() {
text = ""
previousTextField?.becomeFirstResponder()
}
}
#IBDesignable public class OTPView : UIView{
var textFieldArray = [OTPTextField]()
#IBInspectable public var numberOfDots : Int = 4{
didSet{
setUpView()
}
}
#IBInspectable public var bottomLineColor : UIColor = .white{
didSet{
setUpView()
}
}
#IBInspectable public var stackViewSpacing : CGFloat = 10{
didSet{
setUpView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
}
public override func layoutSubviews() {
super.layoutSubviews()
}
fileprivate func setUpView(){
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.backgroundColor = .clear
stackView.isUserInteractionEnabled = true
stackView.translatesAutoresizingMaskIntoConstraints = false
// stackView.alignment = .fill
stackView.distribution = .fillEqually
stackView.spacing = CGFloat(stackViewSpacing)
self.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0),
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0),
stackView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1),
stackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1)
])
setTextFields(stackView: stackView)
}
private func setTextFields(stackView : UIStackView) {
for i in 0..<numberOfDots {
let field = OTPTextField()
textFieldArray.append(field)
stackView.addArrangedSubview(field)
field.delegate = self
field.backgroundColor = .clear
field.textAlignment = .center
field.keyboardType = .numberPad
field.addBottomBorderView(lineColor: bottomLineColor)
i != 0 ? (field.previousTextField = textFieldArray[i-1]) : ()
i != 0 ? (textFieldArray[i-1].nextTextFiled = textFieldArray[i]) : ()
}
}
}
extension OTPView: UITextFieldDelegate {
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let field = textField as? OTPTextField else {
return true
}
if !string.isEmpty {
field.text = string
field.resignFirstResponder()
field.nextTextFiled?.becomeFirstResponder()
return true
}
return true
}
}
extension UITextField {
func addBottomBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.frame.size.height - 1, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
borderStyle = .none
layer.addSublayer(bottomLine)
}
func addBottomBorderView(lineColor : UIColor) {
var bottomBorder = UIView()
//MARK: Setup Bottom-Border
self.translatesAutoresizingMaskIntoConstraints = false
bottomBorder = UIView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
bottomBorder.backgroundColor = lineColor
bottomBorder.translatesAutoresizingMaskIntoConstraints = false
addSubview(bottomBorder)
//Mark: Setup Anchors
bottomBorder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
bottomBorder.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bottomBorder.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bottomBorder.heightAnchor.constraint(equalToConstant: 1).isActive = true // Set Border-Strength
}
}
Yes Need to remove all created previous fileds from array and from your UIStackView
private func setTextFields() {
self.removeFullyAllArrangedSubviews()
textFieldArray.removeAll()
for i in 0..<numberOfOTPdigit {
let field = OTPTextField()
field.keyboardType = .numberPad
field.textColor = textFieldColor
textFieldArray.append(field)
addArrangedSubview(field)
field.delegate = self
field.backgroundColor = .clear
field.textAlignment = .center
field.addBottomBorderView(lineColor: bottomLineColor)
i != 0 ? (field.previousTextField = textFieldArray[i-1]) : ()
i != 0 ? (textFieldArray[i-1].nextTextFiled = textFieldArray[i]) : ()
print("")
}
}

How to create a custom segment control in Swift

I want to create a segment control that works like in the screenshot.
The selected segment should be underlined according to the segment heading text. I have searched for that, but did not find any third party solution.
So how can I develop this type of segment control?
Here you can see that the line at the bottom only stretches across the selected segment.
There is an open source project in GitHub named PageMenu. Please have a look, you can even customize the source file CAPSPageMenu.
https://github.com/PageMenu/PageMenu
To update width of the selection hair line, enable the below property.
menuItemWidthBasedOnTitleTextWidth
Code:
let parameters: [CAPSPageMenuOption] = [
...
.menuItemWidthBasedOnTitleTextWidth(true),
....]
// Initialize scroll menu
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height), pageMenuOptions: parameters)
Please try PageMenuDemoStoryboard demo in the project and update parameters as shown in above code.
import UIKit
extension UIView {
func constraintsEqualToSuperView() {
if let superview = self.superview {
NSLayoutConstraint.activate(
[
self.topAnchor.constraint(
equalTo: superview.topAnchor
),
self.bottomAnchor.constraint(
equalTo: superview.bottomAnchor
),
self.leadingAnchor.constraint(
equalTo: superview.leadingAnchor
),
self.trailingAnchor.constraint(
equalTo: superview.trailingAnchor
)
]
)
}
}
}
protocol ButtonsViewDelegate: class {
func didButtonTap(buttonView: ButtonsView, index: Int)
}
class ButtonsView: UIView {
fileprivate let stackView = UIStackView()
fileprivate var array = [String]()
fileprivate var buttonArray = [UIButton]()
fileprivate let baseTag = 300
weak var delegate: ButtonsViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupUI () {
setupStackView()
setupButton()
}
convenience init(buttons: [String]) {
self.init()
array = buttons
setupUI()
//selectButton(atIndex: 0)
}
fileprivate func setupStackView() {
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fillEqually
stackView.constraintsEqualToSuperView()
}
fileprivate func setupButton() {
for (i,string) in array.enumerated() {
let button = UIButton()
button.setTitle(string.uppercased(), for: .normal)
// button.backgroundColor = UIColor.lightBackgroundColor().lightened(by: 0.2)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(
self,
action: #selector(buttonClicked(sender:)),
for: .touchUpInside
)
button.setTitleColor(
.black,
for: .normal
)
button.titleLabel?.font = UIFont.systemFont(
ofSize: 14,
weight: UIFont.Weight.bold
)
// let view = UIView.init(frame: CGRect.init(x: 0, y: button.frame.size.height - 1, width: button.frame.size.width, height: 1))
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.clear
view.tag = baseTag + i
button.addSubview(view)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(
equalTo: button.leadingAnchor
),
view.trailingAnchor.constraint(
equalTo: button.trailingAnchor
),
view.bottomAnchor.constraint(
equalTo: button.bottomAnchor
),
view.heightAnchor.constraint(
equalToConstant: 1
)
])
stackView.addArrangedSubview(button)
buttonArray.append(button)
}
}
func selectButton(atIndex index: Int) {
if index <= buttonArray.count {
buttonClicked(sender: buttonArray[index])
}
}
#objc private func buttonClicked(sender: UIButton) {
for button in buttonArray {
if button == sender {
button.setTitleColor(
UIColor.darkGray,
for: .normal
)
setUpBottomLine(button: button)
}else{
button.setTitleColor(
.black,
for: .normal
)
hideBottomLine(button: button)
}
}
if let index = buttonArray.index(of: sender) {
delegate?.didButtonTap(buttonView: self, index: index)
}
}
private func setUpBottomLine(button: UIButton) {
if let index = buttonArray.index(of: button) {
if let view = button.viewWithTag(baseTag + index) {
view.backgroundColor = UIColor.red
}
}
}
private func hideBottomLine(button: UIButton) {
if let index = buttonArray.index(of: button) {
if let view = button.viewWithTag(baseTag + index) {
view.backgroundColor = .clear
}
}
}
}
//how to use
let durationBtns = ButtonsView(buttons: [
NSLocalizedString(
"day",
comment: ""
),
NSLocalizedString(
"week",
comment: ""
),
NSLocalizedString(
"month",
comment: ""
),
NSLocalizedString(
"year",
comment: ""
)
])
durationButtons = durationBtns
durationButtons.selectButton(atIndex: 0)
durationBtns.delegate = self
//handle buttton tap
extension viewController: ButtonsViewDelegate {
func didButtonTap(buttonView: ButtonsView, index: Int) {
print(index.description)
}
}

How to change borders of UIPageControl dots?

Changing the colours is pretty straightforward, but is it possible to change the border of all unselected dots?
Ex:
dot.layer.borderWidth = 0.5
dot.layer.borderColor = UIColor.blackColor()
Yes This can be done..
Actually its pretty simple.
For iOS 14 Apple has introduced a great customization, where you can set custom images and even set background
let pageControl = UIPageControl()
pageControl.numberOfPages = 5
pageControl.backgroundStyle = .prominent
pageControl.preferredIndicatorImage = UIImage(systemName: "bookmark.fill")
pageControl.setIndicatorImage(UIImage(systemName: "heart.fill"), forPage: 0)
For prior to iOS 14:-
The Pagecontrol is composed of many Subviews which you can access. self.pageControl.subviews returns you [UIView] i.e array of UIView's.
After you get a single view you can add border to it , change its borderColor, change its border width, transform the dot size like scaling it.. All those properties that a UIView has can be used.
for index in 0..<array.count{ // your array.count
let viewDot = weakSelf?.pageControl.subviews[index]
viewDot?.layer.borderWidth = 0.5
viewDot?.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
if (index == indexPath.row){ // indexPath is the current indexPath of your selected cell or view in the collectionView i.e which needs to be highlighted
viewDot?.backgroundColor = UIColor.black
viewDot?.layer.borderColor = UIColor.black.cgColor
}
else{
viewDot?.backgroundColor = UIColor.white
viewDot?.layer.borderColor = UIColor.black.cgColor
}
}
and it looks like this
And remember you do not need to set weakSelf?.pageControl.currentPage = indexPath.row.Do let me know in case of any problem.. Hope this solves your problem.
All the best
iOS 14 allows setting indicator image with SFSymbol here's my subclassing of UIPageControl
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
}
}
}
}
}
Extension for set pagecontrol indicator border / Swift 3
extension UIImage {
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)
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
}
}
USE:
let image = UIImage.outlinedEllipse(size: CGSize(width: 7.0, height: 7.0), color: .lightGray)
self.pageControl.pageIndicatorTintColor = UIColor.init(patternImage: image!)
self.pageControl.currentPageIndicatorTintColor = .lightGray
If anybody wants to CustomUIPageControl, then might need this
#IBDesignable
class CustomPageControl: UIView {
var dotsView = [RoundButton]()
var currentIndex = 0
#IBInspectable var circleColor: UIColor = UIColor.orange {
didSet {
updateView()
}
}
#IBInspectable var circleBackgroundColor: UIColor = UIColor.clear {
didSet {
updateView()
}
}
#IBInspectable var numberOfDots: Int = 7 {
didSet {
updateView()
}
}
#IBInspectable var borderWidthSize: CGFloat = 1 {
didSet {
updateView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func updateView() -> Void {
for v in self.subviews{
v.removeFromSuperview()
}
dotsView.removeAll()
let stackView = UIStackView()
stackView.axis = NSLayoutConstraint.Axis.horizontal
stackView.distribution = UIStackView.Distribution.fillEqually
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
//Constraints
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
NSLayoutConstraint.activate([
stackView.heightAnchor.constraint(equalToConstant: 20.0)
])
stackView.removeFullyAllArrangedSubviews()
for i in 0..<numberOfDots {
let button:RoundButton = RoundButton(frame: CGRect.zero)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 10.0),
button.widthAnchor.constraint(equalToConstant: 10.0),
])
button.tag = i
button.layer.borderWidth = 1
// button.backgroundColor = circleBackgroundColor
// button.layer.borderWidth = borderWidthSize
// button.layer.borderColor = circleColor.cgColor
button.addTarget(self, action:#selector(self.buttonClicked), for: .touchUpInside)
stackView.addArrangedSubview(button)
dotsView.append(button)
}
}
func updateCurrentDots(borderColor : UIColor, backColor : UIColor, index : Int){
for button in dotsView{
if button == dotsView[index]{
button.backgroundColor = backColor
button.layer.borderColor = borderColor.cgColor
}else{
button.backgroundColor = .clear
button.layer.borderColor = borderColor.cgColor
}
}
}
#objc func buttonClicked() {
print("Button Clicked")
}
class RoundButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
override func layoutSubviews() {
self.layer.cornerRadius = self.frame.size.width / 2
}
}
extension UIStackView {
func removeFully(view: UIView) {
removeArrangedSubview(view)
view.removeFromSuperview()
}
func removeFullyAllArrangedSubviews() {
arrangedSubviews.forEach { (view) in
removeFully(view: view)
}
}
}
You can use either Programmatically or Stoaryboard
To update the current dots calls this function
self.pageControl.updateCurrentDots(borderColor: .white, backColor: .white, index:1)

Resources