How to create a custom segment control in Swift - ios

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)
}
}

Related

Scroll the ScrollView when tap a button Swift with ViewCode

I'm creating a ViewPager in Swift using ViewCode.
I need to create the following action: tap on the Next button (method: actionNextPressed() ), and scroll the scrollview to the next page (like a TapGesture scrolling to right or left).
Here is my Swift code, what I've tried until now, and the printscreen from the viewpager (at the moment)
PagerViewController.swift
import UIKit
import SnapKit
open class PagerViewController: UIViewController, UIPageViewControllerDelegate {
private lazy var dimmedView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = maxDimmedAlpha
return view
}()
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 1, alpha: 0)
return view
}()
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.isPagingEnabled = true
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(pages.count), height: containerView.frame.height)
for i in 0..<pages.count {
var page = pages[i]
let dialog = PageViewController(
icon: page.icon,
titleText: page.title,
descriptionText: page.description,
titleActionButton: page.titleButton,
actionButton: page.actionButton
)!
scrollView.addSubview(dialog.view)
scrollView.subviews[i].frame = CGRect(
x: view.frame.width * CGFloat(i),
y: 0,
width: containerView.frame.width,
height: containerView.frame.height
)
}
scrollView.delegate = self
return scrollView
}()
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.currentPage = 0
pageControl.numberOfPages = pages.count
pageControl.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .allEvents)
pageControl.isUserInteractionEnabled = false
pageControl.pageIndicatorTintColor = .systemGray
pageControl.currentPageIndicatorTintColor = .systemBlue
pageControl.backgroundColor = .white
return pageControl
}()
private lazy var directionsButtonsStackView: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.distribution = .fillEqually
view.backgroundColor = .red
return view
}()
private lazy var buttonJump = UIButton()
private lazy var buttonBefore = UIButton()
private lazy var buttonNext = UIButton()
private var titleJumpButton: String! = ""
private let maxDimmedAlpha: CGFloat = 0.6
open var pages: Array<PageModel>!
private var currentPage: Int = 0
init?(listPages: Array<PageModel>, titleJumpButton: String) {
super.init(nibName: nil, bundle: nil)
self.pages = listPages
self.titleJumpButton = titleJumpButton
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func pageControlTapHandler(sender: UIPageControl) {
scrollView.scrollTo(horizontalPage: sender.currentPage)
}
open override func viewDidLoad() {
super.viewDidLoad()
setupView()
addViewComponents()
setupConstraints()
}
private func setupView() {
self.buttonJump = PageButton(frame: .zero).build(
context: self,
title: titleJumpButton!,
selector: #selector(actionJumpPressed)
)
self.buttonBefore = PageButton(frame: .zero).build(
context: self,
title: "Before",
selector: #selector(actionBeforePressed)
)
self.buttonBefore.backgroundColor = .white
self.buttonNext = PageButton(frame: .zero).build(
context: self,
title: "Next",
selector: #selector(actionNextPressed)
)
self.buttonJump.setTitle(titleJumpButton, for: .normal)
}
private func addViewComponents() {
view.addSubview(dimmedView)
containerView.addSubview(scrollView)
containerView.addSubview(pageControl)
directionsButtonsStackView.addArrangedSubview(buttonBefore)
directionsButtonsStackView.addArrangedSubview(buttonNext)
containerView.addSubview(directionsButtonsStackView)
containerView.addSubview(buttonJump)
view.addSubview(containerView)
}
private func setupConstraints() {
dimmedView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.bottom.equalToSuperview()
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
}
containerView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(100)
make.bottom.equalToSuperview().inset(100)
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.centerX.equalToSuperview()
make.centerY.equalToSuperview()
make.margins.equalTo(20)
}
scrollView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.bottom.equalTo(pageControl.snp.top)
make.leading.equalTo(containerView.snp.leading)
make.trailing.equalTo(containerView.snp.trailing)
}
pageControl.snp.makeConstraints { make in
make.bottom.equalTo(directionsButtonsStackView.snp.top)
make.leading.equalTo(containerView.snp.leading).offset(20)
make.trailing.equalTo(containerView.snp.trailing).inset(20)
make.height.equalTo(30)
make.centerX.equalToSuperview()
}
directionsButtonsStackView.snp.makeConstraints { make in
make.bottom.equalTo(buttonJump.snp.top)
make.leading.equalTo(containerView.snp.leading).offset(20)
make.trailing.equalTo(containerView.snp.trailing).inset(20)
make.height.equalTo(60)
make.width.greaterThanOrEqualTo(0)
make.centerX.equalToSuperview()
}
buttonJump.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(10)
make.leading.equalTo(containerView).offset(20)
make.trailing.equalTo(containerView).inset(20)
make.centerX.equalToSuperview()
make.width.greaterThanOrEqualTo(0)
make.height.equalTo(50)
}
}
#objc private func actionJumpPressed() {
self.dismiss(animated: true, completion: nil)
}
#objc private func actionBeforePressed() {
if currentPage == 0 {
return
} else {
currentPage -= 1
pageControl.currentPage = currentPage
}
}
#objc private func actionNextPressed() {
if currentPage == pages.count {
return
} else {
currentPage += 1
pageControl.currentPage = currentPage
}
}
}
extension PagerViewController: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = Int(round(scrollView.contentOffset.x / view.frame.width))
pageControl.currentPage = pageIndex
currentPage = pageIndex
}
}
extension UIScrollView {
func scrollTo(horizontalPage: Int? = 0) {
var frame: CGRect = self.frame
frame.origin.x = frame.size.width * CGFloat(horizontalPage ?? 0)
self.scrollRectToVisible(frame, animated: true)
}
}
I had the same issue once with a collectionView. You have to turn off the .isPagingEnabled, scroll the scrollview where you want and turn it back on the paging.
func scrollTo(horizontalPage: Int? = 0) {
scrollView.isPagingEnabled = false
var frame: CGRect = self.frame
frame.origin.x = frame.size.width * CGFloat(horizontalPage ?? 0)
self.scrollRectToVisible(frame, animated: true)
scrollView.isPagingEnabled = true
}

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()
}
}

Passcode screen with UIStackView, Swift

I am trying to implement passcode screen, but I am having trouble with alignment, as you can see in this picture.
What I'm trying to do is, have three buttons in each row, so it actually looks like a "keypad". I am not quite sure how could I do this. I thought about making inside of first stack view which is vertical, four others horizontal stack views, but couldn't manage to do it. Any suggestion or help would be appreciated. Thanks :)
Code is below.
class ViewController: UIViewController {
var verticalStackView: UIStackView = {
var verticalStackView = UIStackView()
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.axis = .vertical
verticalStackView.distribution = .fillEqually
verticalStackView.spacing = 13
verticalStackView.alignment = .fill
verticalStackView.contentMode = .scaleToFill
verticalStackView.backgroundColor = .red
return verticalStackView
}()
var horizontalStackView: UIStackView = {
var buttons = [PasscodeButtons]()
var horizontalStackView = UIStackView(arrangedSubviews: buttons)
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.axis = .horizontal
horizontalStackView.distribution = .fillEqually
horizontalStackView.alignment = .fill
horizontalStackView.spacing = 25
horizontalStackView.contentMode = .scaleToFill
horizontalStackView.backgroundColor = .green
return horizontalStackView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureStackView()
configureConstraints()
}
func configureStackView() {
view.addSubview(verticalStackView)
verticalStackView.addSubview(horizontalStackView)
addButtonsToStackView()
}
func addButtonsToStackView() {
let numberOfButtons = 9
for i in 0...numberOfButtons {
let button = PasscodeButtons()
button.setTitle("\(i)", for: .normal)
button.tag = i
horizontalStackView.addArrangedSubview(button)
}
}
func configureConstraints() {
verticalStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 200).isActive = true
verticalStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50).isActive = true
verticalStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -50).isActive = true
verticalStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100).isActive = true
horizontalStackView.topAnchor.constraint(equalTo: verticalStackView.topAnchor, constant: 10).isActive = true
horizontalStackView.leadingAnchor.constraint(equalTo: verticalStackView.leadingAnchor, constant: 10).isActive = true
}
}
In case PasscodeButtons matters, here is code from there too.
class PasscodeButtons: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupButton()
}
override func awakeFromNib() {
super.awakeFromNib()
setupButton()
}
private func setupButton() {
setTitleColor(UIColor.black, for: .normal)
setTitleColor(UIColor.black, for: .highlighted)
}
private func updateView() {
layer.cornerRadius = frame.width / 2
layer.masksToBounds = true
layer.borderColor = UIColor(red: 0/255.0, green: 0/255.0, blue: 0, alpha:1).cgColor
layer.borderWidth = 2.0
}
override func layoutSubviews() {
super.layoutSubviews()
updateView()
backgroundColor = .cyan
}
}
The general idea is:
need 4 horizontal stack view "button rows" ... 3 rows with 3 buttons each plus one row with 1 button (the "Zero" button)
create a vertical stack view to hold the "rows" of buttons
set all stack view distributions to .fillEqually
set all stack view spacing to the same value
Then, to generate everything, create an array of arrays of Ints for the key numbers, laid out like a keypad:
let keyNums: [[Int]] = [
[7, 8, 9],
[4, 5, 6],
[1, 2, 3],
[0],
]
Loop through, creating each row of buttons.
Here's a quick example (I modified your PasscodeButton class slightly):
class PasscodeButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupButton()
}
override func awakeFromNib() {
super.awakeFromNib()
setupButton()
}
private func setupButton() {
setTitleColor(UIColor.black, for: .normal)
setTitleColor(UIColor.lightGray, for: .highlighted)
layer.masksToBounds = true
layer.borderColor = UIColor(red: 0/255.0, green: 0/255.0, blue: 0, alpha:1).cgColor
layer.borderWidth = 2.0
backgroundColor = .cyan
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height * 0.5
}
}
class PassCodeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let outerStack = UIStackView()
outerStack.axis = .vertical
outerStack.distribution = .fillEqually
outerStack.spacing = 16
let keyNums: [[Int]] = [
[7, 8, 9],
[4, 5, 6],
[1, 2, 3],
[0],
]
keyNums.forEach { rowNums in
let hStack = UIStackView()
hStack.distribution = .fillEqually
hStack.spacing = outerStack.spacing
rowNums.forEach { n in
let btn = PasscodeButton()
btn.setTitle("\(n)", for: [])
// square / round (1:1 ratio) buttons
// for all buttons except the bottom "Zero" button
if rowNums.count != 1 {
btn.heightAnchor.constraint(equalTo: btn.widthAnchor).isActive = true
}
btn.addTarget(self, action: #selector(numberTapped(_:)), for: .touchUpInside)
hStack.addArrangedSubview(btn)
}
outerStack.addArrangedSubview(hStack)
}
outerStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerStack)
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// no bottom or height constraint
])
}
#objc func numberTapped(_ sender: UIButton) -> Void {
guard let n = sender.currentTitle else {
// button has no title?
return
}
print("Number \(n) was tapped!")
}
}
Output:
You'll likely want to play with the sizing, but that should get you on your way.
Edit - comment "I would like for 0 to stay in last row in the middle, and on the left side I would pop in touch id icon and on the right backspace button, how could I leave last row out of a shuffle?"
When you create your "grid" of buttons:
create the top three "rows" but leave the button titles blank.
create the "bottom row" of 3 buttons
set first button with "touchID" image
set title of second button to "0"
set third button with "backSpace" image
then call a function to set the "number" buttons
Change the keyNums array to:
let keyOrder: [Int] = [
7, 8, 9,
4, 5, 6,
1, 2, 3,
]
// you may want to show the "standard order" first,
// so pass a Bool parameter
// shuffle the key order if specified
let keyNums = shouldShuffle
? keyOrder.shuffled()
: keyOrder
// loop through and update the button titles
// with the new order
Here's some updated code, using a "KeyPad" UIView subclass:
enum PasscodeButtonType {
case NUMBER, TOUCH, BACKSPACE
}
class PasscodeButton: UIButton {
var pcButtonType: PasscodeButtonType = .NUMBER
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupButton()
}
override func awakeFromNib() {
super.awakeFromNib()
setupButton()
}
private func setupButton() {
setTitleColor(UIColor.black, for: .normal)
setTitleColor(UIColor.lightGray, for: .highlighted)
layer.masksToBounds = true
layer.borderColor = UIColor(red: 0/255.0, green: 0/255.0, blue: 0, alpha:1).cgColor
layer.borderWidth = 2.0
backgroundColor = .cyan
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height * 0.5
// button font and image sizes... adjust as desired
let ptSize = bounds.height * 0.4
titleLabel?.font = .systemFont(ofSize: ptSize)
let config = UIImage.SymbolConfiguration(pointSize: ptSize)
setPreferredSymbolConfiguration(config, forImageIn: [])
}
}
class KeyPadView: UIView {
// closures so we can tell the controller something happened
var touchIDTapped: (()->())?
var backSpaceTapped: (()->())?
var numberTapped: ((String)->())?
var spacing: CGFloat = 16
private let outerStack = UIStackView()
init(spacing spc: CGFloat) {
self.spacing = spc
super.init(frame: .zero)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// load your TouchID and Backspace button images
var touchImg: UIImage!
var backImg: UIImage!
if let img = UIImage(named: "myTouchImage") {
touchImg = img
} else {
if #available(iOS 14.0, *) {
touchImg = UIImage(systemName: "touchid")
} else if #available(iOS 13.0, *) {
touchImg = UIImage(systemName: "snow")
} else {
fatalError("No TouchID button image available!")
}
}
if let img = UIImage(named: "myBackImage") {
backImg = img
} else {
if #available(iOS 13.0, *) {
backImg = UIImage(systemName: "delete.left.fill")
} else {
fatalError("No BackSpace button image available!")
}
}
outerStack.axis = .vertical
outerStack.distribution = .fillEqually
outerStack.spacing = spacing
// add 3 "rows" of NUMBER buttons
for _ in 1...3 {
let hStack = UIStackView()
hStack.distribution = .fillEqually
hStack.spacing = outerStack.spacing
for _ in 1...3 {
let btn = PasscodeButton()
// these are NUMBER buttons
btn.pcButtonType = .NUMBER
// square / round (1:1 ratio) buttons
// for all buttons except the bottom "Zero" button
btn.heightAnchor.constraint(equalTo: btn.widthAnchor).isActive = true
btn.addTarget(self, action: #selector(keyButtonTapped(_:)), for: .touchUpInside)
hStack.addArrangedSubview(btn)
}
outerStack.addArrangedSubview(hStack)
}
// now add bottom row of TOUCH / 0 / BACKSPACE buttons
let hStack = UIStackView()
hStack.distribution = .fillEqually
hStack.spacing = outerStack.spacing
var btn: PasscodeButton!
btn = PasscodeButton()
btn.pcButtonType = .TOUCH
btn.setImage(touchImg, for: [])
btn.heightAnchor.constraint(equalTo: btn.widthAnchor).isActive = true
btn.addTarget(self, action: #selector(keyButtonTapped(_:)), for: .touchUpInside)
hStack.addArrangedSubview(btn)
btn = PasscodeButton()
btn.pcButtonType = .NUMBER
btn.setTitle("0", for: [])
btn.heightAnchor.constraint(equalTo: btn.widthAnchor).isActive = true
btn.addTarget(self, action: #selector(keyButtonTapped(_:)), for: .touchUpInside)
hStack.addArrangedSubview(btn)
btn = PasscodeButton()
btn.pcButtonType = .BACKSPACE
btn.setImage(backImg, for: [])
btn.heightAnchor.constraint(equalTo: btn.widthAnchor).isActive = true
btn.addTarget(self, action: #selector(keyButtonTapped(_:)), for: .touchUpInside)
hStack.addArrangedSubview(btn)
// add bottom buttons row
outerStack.addArrangedSubview(hStack)
outerStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(outerStack)
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: topAnchor, constant: spacing),
outerStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: spacing),
outerStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -spacing),
outerStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -spacing),
])
// use "standard number pad order" for the first time
updateNumberKeys(shouldShuffle: false)
}
func updateNumberKeys(shouldShuffle b: Bool = true) -> Void {
let keyOrder: [Int] = [
7, 8, 9,
4, 5, 6,
1, 2, 3,
0,
]
// shuffle the key order if specified
let keyNumbers = b == true
? keyOrder.shuffled()
: keyOrder
// index to step through array
var numIDX: Int = 0
// get first 3 rows of buttons
let rows = outerStack.arrangedSubviews.prefix(3)
// loop through buttons, changing their titles
rows.forEach { v in
guard let hStack = v as? UIStackView else {
fatalError("Bad Setup!")
}
hStack.arrangedSubviews.forEach { b in
guard let btn = b as? PasscodeButton else {
fatalError("Bad Setup!")
}
btn.setTitle("\(keyNumbers[numIDX])", for: [])
numIDX += 1
}
}
// change title of center button on bottom row
guard let lastRowStack = outerStack.arrangedSubviews.last as? UIStackView,
lastRowStack.arrangedSubviews.count == 3,
let btn = lastRowStack.arrangedSubviews[1] as? PasscodeButton
else {
fatalError("Bad Setup!")
}
btn.setTitle("\(keyNumbers[numIDX])", for: [])
}
#objc func keyButtonTapped(_ sender: Any?) -> Void {
guard let btn = sender as? PasscodeButton else {
return
}
switch btn.pcButtonType {
case .TOUCH:
// tell the controller TouchID was tapped
touchIDTapped?()
case .BACKSPACE:
// tell the controller BackSpace was tapped
backSpaceTapped?()
default:
guard let n = btn.currentTitle else {
// button has no title?
return
}
// tell the controller a NUmber Key was tapped
numberTapped?(n)
}
// update the number keys, but shuffle them
updateNumberKeys()
}
}
class PassCodeViewController: UIViewController {
var keyPad: KeyPadView!
override func viewDidLoad() {
super.viewDidLoad()
// play with these to see how the button sizes / spacing looks
let keyPadSpacing: CGFloat = 12
let keyPadWidth: CGFloat = 240
// init with button spacing as desired
keyPad = KeyPadView(spacing: keyPadSpacing)
keyPad.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(keyPad)
let g = view.safeAreaLayoutGuide
// center keyPad view
// its height will be set by its layout
NSLayoutConstraint.activate([
keyPad.widthAnchor.constraint(equalToConstant: keyPadWidth),
keyPad.centerXAnchor.constraint(equalTo: g.centerXAnchor),
keyPad.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
// let's show the frame of the keyPad
keyPad.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
// set closures
keyPad.numberTapped = { [weak self] str in
guard let self = self else {
return
}
print("Number key tapped:", str)
// do something with the number string
}
keyPad.touchIDTapped = { [weak self] in
guard let self = self else {
return
}
print("TouchID was tapped!")
// do something because TouchID button was tapped
}
keyPad.backSpaceTapped = { [weak self] in
guard let self = self else {
return
}
print("BackSpace was tapped!")
// do something because BackSpace button was tapped
}
}
}
and here's how it looks, setting the keypad view width to 240 and the button spacing to 12:
follow the steps:-
take a vertical stack view and add three buttons in it (for first line buttons)
take a vertical stack view and add three buttons in it (for second line buttons)
take a vertical stack view and add three buttons in it (for third line buttons)
take a vertical stack view and add three buttons in it (for fourth line buttons)
take a horizontal stackview and add all these 4 stackviews in it.

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

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
}
}

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