How to center text in a custom segment controller? Swift - swift

I am making a custom segment controller. I faced into the following problem: the text inside the selectable view is not centered. I would like to find a universal solution so that the text remains in the center of the selected segment, regardless of the number of cells. Please , hellp. Thanks a lot!
Image: Example of problem
Code:
#IBDesignable
class CustomSegmentedCntrl: UIControl {
private var buttons = [UIButton]()
private var selector: UIView!
var selectedSegmentIndex = 0
private var segments = [String]() {
didSet {
updateView()
}
}
#IBInspectable
var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable
var borderColor: UIColor = UIColor.clear {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable
var textColor: UIColor = .clear {
didSet {
updateView()
}
}
#IBInspectable
var selectorColor: UIColor = .clear {
didSet {
updateView()
}
}
#IBInspectable
var selectorTextColor: UIColor = .clear {
didSet {
updateView()
}
}
func configure(with segmentButtons:[String]) {
self.segments = segmentButtons
}
func setProperties(borderWidth: CGFloat, borderColor: UIColor, textColor: UIColor, selectorColor: UIColor, selectorTextColor: UIColor) {
self.borderWidth = borderWidth
self.borderColor = borderColor
self.selectorColor = selectorColor
self.textColor = textColor
self.selectorTextColor = selectorTextColor
}
func updateView() {
buttons.removeAll()
subviews.forEach { $0.removeFromSuperview()}
let buttonTitles = segments
for buttonTitle in buttonTitles {
let button = UIButton(type: .system)
button.setTitle(buttonTitle, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.addTarget(self, action: #selector(buttonTapped(button:)), for: .touchUpInside)
buttons.append(button)
}
let selectorWidth = frame.width / CGFloat(buttonTitles.count)
selector = UIView(frame: CGRect(x: 0, y: 0, width: selectorWidth, height: frame.height))
selector.layer.cornerRadius = frame.height/2
selector.backgroundColor = selectorColor
addSubview(selector)
///Stack view constraints
let stackView = UIStackView(arrangedSubviews: buttons)
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fillProportionally
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
override func draw(_ rect: CGRect) {
layer.cornerRadius = frame.height/2
}
#objc func buttonTapped(button: UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == button {
print(buttonIndex)
selectedSegmentIndex = buttonIndex
let selectorStartPosition = frame.width/CGFloat(buttons.count) * CGFloat(buttonIndex)
UIView.animate(withDuration: 0.2, animations: {
self.selector.frame.origin.x = selectorStartPosition
})
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
}
}
ViewController is look like this:
//View at the storyboard
#IBOutlet weak var secondSegmentedControll: CustomSegmentedCntrl!
func installSecondCell() {
self.secondSegmentedControll.borderColor = .green
self.secondSegmentedControll.selectorTextColor = .blue
self.secondSegmentedControll.selectorColor = .red
self.secondSegmentedControll.configure(with: ["one", "two", "three", "four"])
self.secondSegmentedControll.borderWidth = 1
}
override func viewDidLoad() {
super.viewDidLoad()
installSecondCell()
}

Solution: Add this: stackView.distribution = .fillEqually to updateView()

Related

iOS UIkit custom segmented buttons

I'm looking to create a view with these buttons. There is a background animation when one of the button touched.
Not sure how to do this.
Is custom segmented buttons the way to go?
I went with custom control
import UIKit
protocol MSegmentedControlDelegate:AnyObject {
func segSelectedIndexChange(to index:Int)
}
class MSegmentedControl: UIControl {
private var buttonTitles:[String]!
private var buttons: [UIButton]!
private var selectorView: UIView!
var textColor:UIColor = .black
var selectorViewColor: UIColor = .white
var selectorTextColor: UIColor = .red
weak var delegate:MSegmentedControlDelegate?
public private(set) var selectedIndex : Int = 0
convenience init(frame:CGRect,buttonTitle:[String]) {
self.init(frame: frame)
self.buttonTitles = buttonTitle
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.backgroundColor = UIColor.white
updateView()
}
func setButtonTitles(buttonTitles:[String]) {
self.buttonTitles = buttonTitles
self.updateView()
}
func setIndex(index:Int) {
buttons.forEach({ $0.setTitleColor(textColor, for: .normal) })
let button = buttons[index]
selectedIndex = index
button.setTitleColor(selectorTextColor, for: .normal)
let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(index)
UIView.animate(withDuration: 0.2) {
self.selectorView.frame.origin.x = selectorPosition
}
}
#objc func buttonAction(sender:UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == sender {
let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(buttonIndex)
selectedIndex = buttonIndex
delegate?.segSelectedIndexChange(to: selectedIndex)
UIView.animate(withDuration: 0.3) {
self.selectorView.frame.origin.x = selectorPosition
}
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
}
}
//Configuration View
extension MSegmentedControl {
private func updateView() {
createButton()
configSelectorView()
configStackView()
}
private func configStackView() {
let stack = UIStackView(arrangedSubviews: buttons)
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stack.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stack.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
stack.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
private func configSelectorView() {
let selectorWidth = frame.width / CGFloat(self.buttonTitles.count)
selectorView = UIView(frame: CGRect(x: 0, y: 8, width: selectorWidth, height: 32))
selectorView.backgroundColor = selectorViewColor
selectorView.layer.cornerRadius = 16
selectorView.layer.opacity = 0.5
addSubview(selectorView)
}
private func createButton() {
buttons = [UIButton]()
buttons.removeAll()
subviews.forEach({$0.removeFromSuperview()})
for buttonTitle in buttonTitles {
let button = UIButton(type: .system)
button.setTitle(buttonTitle, for: .normal)
button.addTarget(self, action:#selector(MSegmentedControl.buttonAction(sender:)), for: .touchUpInside)
button.setTitleColor(textColor, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
buttons.append(button)
}
buttons[0].setTitleColor(selectorTextColor, for: .normal)
}
}
Usage:
private let segControl: MSegmentedControl = {
let segControl = MSegmentedControl(
frame: CGRect(x: 0, y: 240, width: 280, height: 50),
buttonTitle: ["Average","Total","Pending"])
segControl.textColor = M.Colors.greyWhite
segControl.selectorTextColor = .white
return segControl
}()
To access index change event:
Implement the delegate on parent view:
addSubview(segControl)
segControl.delegate = self
Delegate:
func segSelectedIndexChange(to index: Int) {
switch index {
case 0: print("Average")
case 1: print("Total")
case 2: print("Pending")
default: break
}
}
Result:

How to colour radio buttons?

You can see that Apple has coloured the radio buttons. I would like to do the same. I can't seems to find the option to change the colour in Interface Builder on storyboard.
As for doing it, programmatically, I tried enabling layer .wantsLayer = true and then tried to set the colour by .layer?.borderColour = NSColor.systemBlue.cgColor and tried .layer?.backgroundColor = NSColor.systemRed.cgColor and other similar properties but no avail.
Likewise, how do you add the colour rectangles on NSMenuItem on NSPopUpButton?
The following code demonstrates a group of custom radio buttons for MacOS made by subclassing NSButton. It may be run in an Xcode swift project by copy/pasting into a newly added file called ‘main.swift’ and deleting the original AppDelegate.
import Cocoa
class CustomButton: NSButton {
var circleColor: NSColor!
override func draw(_ rect: NSRect) {
let circle = NSBezierPath(ovalIn: bounds)
switch(self.tag) {
case 0:
circleColor = NSColor.red
case 1:
circleColor = NSColor.green
case 2:
circleColor = NSColor.yellow
case 3:
circleColor = NSColor.orange
default:
break
}
circleColor.set()
circle.fill()
if(self.state) == .on {
let dotRect = NSInsetRect(bounds, 18.0, 18.0);
let dot = NSBezierPath (ovalIn:dotRect)
let dotColor = NSColor.black
dotColor.set()
dot.fill()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
#objc func radioGrpAction(_ sender:NSButton) {
print("You selected: id = \(sender.tag)")
}
func buildMenu() {
let mainMenu = NSMenu()
NSApp.mainMenu = mainMenu
// **** App menu **** //
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenuItem.submenu = appMenu
appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q")
}
func buildWnd() {
let _wndW : CGFloat = 300
let _wndH : CGFloat = 200
window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable], backing:.buffered, defer:false)
window.center()
window.title = "Radio Button Group"
window.makeKeyAndOrderFront(window)
// === Radio Grp Box === //
let grpBox = NSBox(frame: NSMakeRect( 50,_wndH - 100, 150, 60))
grpBox.title = "Radio Group"
window.contentView!.addSubview (grpBox)
// === Radio Horizontal Grid === //
let _btnW : CGFloat = 24
let _btnH : CGFloat = 24
let _left : CGFloat = 10 // left margin first button
let _YOffset : CGFloat = 5 // 0,0 at left, bottom of group box
let _spacing : CGFloat = 5 // spacing between buttons
for x in stride(from:0, through:3, by:1) {
let _XOffset = _left + CGFloat(x)*(_btnW + _spacing)
let btn = CustomButton(frame:NSMakeRect(_XOffset, _YOffset, _btnW, _btnH))
btn.setButtonType(.radio)
btn.tag = x
if(x == 0){btn.state = .on}
btn.action = #selector(self.radioGrpAction(_:))
grpBox.contentView!.addSubview(btn)
}
// === Quit btn === //
let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
quitBtn.bezelStyle = .circular
quitBtn.title = "Q"
quitBtn.action = #selector(NSApplication.terminate)
window.contentView!.addSubview(quitBtn)
}
func applicationDidFinishLaunching(_ notification: Notification) {
buildMenu()
buildWnd()
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
let appDelegate = AppDelegate()
// ***** main.swift ***** //
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.delegate = appDelegate
app.activate(ignoringOtherApps:true)
app.run()
Ok the same but for AppKit:
import Cocoa
import AppKit
extension NSView {
func centerX(inView view: NSView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: NSView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
}
class CustomRadioButton: NSView {
private let containerSize: CGFloat = 60.0
private let selectorSize: CGFloat = 20.0
var containerColor: NSColor = .blue
var selectorColor: NSColor = .red
var selected: Bool = true {
didSet {
selectorView.isHidden = !selected
}
}
private lazy var containerView: NSView = {
let view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = containerColor.cgColor
return view
}()
private lazy var selectorView: NSView = {
let view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = selectorColor.cgColor
return view
}()
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: containerSize, height: containerSize))
configureUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureUI() {
addSubview(containerView)
containerView.setDimensions(height: containerSize, width: containerSize)
containerView.layer?.cornerRadius = containerSize / 2
containerView.centerX(inView: self)
containerView.centerY(inView: self)
addSubview(selectorView)
selectorView.setDimensions(height: selectorSize, width: selectorSize)
selectorView.layer?.cornerRadius = selectorSize / 2
selectorView.centerY(inView: containerView)
selectorView.centerX(inView: containerView)
}
}
let selector = CustomRadioButton()
selector.selected = false

How can I position these UIView elements from the right using CGRect to position

I have a UIView sub class that allows me to create a group of 'tags' for the footer of some content. At the moment however they are position aligned to the left edge, I would like them to be positioned from the right.
I have included a playground below that should run the screen shot you can see.
The position is set within the layoutSubviews method of CloudTagView.
I tried to play around with their position but have not been able to start them from the right however.
import UIKit
import PlaygroundSupport
// CLOUD VIEW WRAPPER - THIS IS THE CONTAINER FOR THE TAGS AND SETS UP THEIR FRAME
class CloudTagView: UIView {
weak var delegate: TagViewDelegate?
override var intrinsicContentSize: CGSize {
return frame.size
}
var removeOnDismiss = true
var resizeToFit = true
var tags = [TagView]() {
didSet {
layoutSubviews()
}
}
var padding = 5 {
didSet {
layoutSubviews()
}
}
var maxLengthPerTag = 0 {
didSet {
layoutSubviews()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
clipsToBounds = true
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
clipsToBounds = true
}
override func layoutSubviews() {
for tag in subviews {
tag.removeFromSuperview()
}
var xAxis = padding
var yAxis = padding
var maxHeight = 0
for (index, tag) in tags.enumerated() {
setMaxLengthIfNeededIn(tag)
tag.delegate = self
if index == 0 {
maxHeight = Int(tag.frame.height)
}else{
let expectedWidth = xAxis + Int(tag.frame.width) + padding
if expectedWidth > Int(frame.width) {
yAxis += maxHeight + padding
xAxis = padding
maxHeight = Int(tag.frame.height)
}
if Int(tag.frame.height) > maxHeight {
maxHeight = Int(tag.frame.height)
}
}
tag.frame = CGRect(x: xAxis, y: yAxis, width: Int(tag.frame.size.width), height: Int(tag.frame.size.height))
addSubview(tag)
tag.layoutIfNeeded()
xAxis += Int(tag.frame.width) + padding
}
if resizeToFit {
frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: CGFloat(yAxis + maxHeight + padding))
}
}
// MARK: Methods
fileprivate func setMaxLengthIfNeededIn(_ tag: TagView) {
if maxLengthPerTag > 0 && tag.maxLength != maxLengthPerTag {
tag.maxLength = maxLengthPerTag
}
}
}
// EVERYTHING BELOW HERE IS JUST SETUP / REQUIRED TO RUN IN PLAYGROUND
class ViewController:UIViewController{
let cloudView: CloudTagView = {
let view = CloudTagView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
let tags = ["these", "are", "my", "tags"]
tags.forEach { tag in
let t = TagView(text: tag)
t.backgroundColor = .darkGray
t.tintColor = .white
cloudView.tags.append(t)
}
view.backgroundColor = .white
view.addSubview(cloudView)
NSLayoutConstraint.activate([
cloudView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
cloudView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
cloudView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
cloudView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
}
}
// Tag View
class TagView: UIView {
weak var delegate: TagViewDelegate?
var text = "" {
didSet {
layoutSubviews()
}
}
var marginTop = 5 {
didSet {
layoutSubviews()
}
}
var marginLeft = 10 {
didSet {
layoutSubviews()
}
}
var iconImage = UIImage(named: "close_tag_2", in: Bundle(for: CloudTagView.self), compatibleWith: nil) {
didSet {
layoutSubviews()
}
}
var maxLength = 0 {
didSet {
layoutSubviews()
}
}
override var backgroundColor: UIColor? {
didSet {
layoutSubviews()
}
}
override var tintColor: UIColor? {
didSet {
layoutSubviews()
}
}
var font: UIFont = UIFont.systemFont(ofSize: 12) {
didSet {
layoutSubviews()
}
}
fileprivate let dismissView: UIView
fileprivate let icon: UIImageView
fileprivate let textLabel: UILabel
public override init(frame: CGRect) {
dismissView = UIView()
icon = UIImageView()
textLabel = UILabel()
super.init(frame: frame)
isUserInteractionEnabled = true
addSubview(textLabel)
addSubview(icon)
addSubview(dismissView)
dismissView.isUserInteractionEnabled = true
textLabel.isUserInteractionEnabled = true
dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.iconTapped)))
textLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.labelTapped)))
backgroundColor = UIColor(white: 0.0, alpha: 0.6)
tintColor = UIColor.white
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public init(text: String) {
dismissView = UIView()
icon = UIImageView()
textLabel = UILabel()
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
isUserInteractionEnabled = true
addSubview(textLabel)
addSubview(icon)
addSubview(dismissView)
dismissView.isUserInteractionEnabled = true
textLabel.isUserInteractionEnabled = true
dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.iconTapped)))
textLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(TagView.labelTapped)))
self.text = text
backgroundColor = UIColor(white: 0.0, alpha: 0.6)
tintColor = UIColor.white
}
override func layoutSubviews() {
icon.frame = CGRect(x: marginLeft, y: marginTop + 4, width: 8, height: 8)
icon.image = iconImage?.withRenderingMode(.alwaysTemplate)
icon.tintColor = tintColor
let textLeft: Int
if icon.image != nil {
dismissView.isUserInteractionEnabled = true
textLeft = marginLeft + Int(icon.frame.width ) + marginLeft / 2
} else {
dismissView.isUserInteractionEnabled = false
textLeft = marginLeft
}
textLabel.frame = CGRect(x: textLeft, y: marginTop, width: 100, height: 20)
textLabel.backgroundColor = UIColor(white: 0, alpha: 0.0)
if maxLength > 0 && text.count > maxLength {
textLabel.text = text.prefix(maxLength)+"..."
}else{
textLabel.text = text
}
textLabel.textAlignment = .center
textLabel.font = font
textLabel.textColor = tintColor
textLabel.sizeToFit()
let tagHeight = Int(max(textLabel.frame.height,14)) + marginTop * 2
let tagWidth = textLeft + Int(max(textLabel.frame.width,14)) + marginLeft
let dismissLeft = Int(icon.frame.origin.x) + Int(icon.frame.width) + marginLeft / 2
dismissView.frame = CGRect(x: 0, y: 0, width: dismissLeft, height: tagHeight)
frame = CGRect(x: Int(frame.origin.x), y: Int(frame.origin.y), width: tagWidth, height: tagHeight)
layer.cornerRadius = bounds.height / 2
}
// MARK: Actions
#objc func iconTapped(){
delegate?.tagDismissed?(self)
}
#objc func labelTapped(){
delegate?.tagTouched?(self)
}
}
// MARK: TagViewDelegate
#objc protocol TagViewDelegate {
#objc optional func tagTouched(_ tag: TagView)
#objc optional func tagDismissed(_ tag: TagView)
}
extension CloudTagView: TagViewDelegate {
public func tagDismissed(_ tag: TagView) {
delegate?.tagDismissed?(tag)
if removeOnDismiss {
if let index = tags.firstIndex(of: tag) {
tags.remove(at: index)
}
}
}
public func tagTouched(_ tag: TagView) {
delegate?.tagTouched?(tag)
}
}
let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
PlaygroundPage.current.needsIndefiniteExecution = true
UIStackView can line subviews up in a row for you, including with trailing alignment. Here is a playground example:
import SwiftUI
import PlaygroundSupport
class V: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tags = ["test", "testing", "test more"].map { word -> UIView in
let label = UILabel()
label.text = word
label.translatesAutoresizingMaskIntoConstraints = false
let background = UIView()
background.backgroundColor = .cyan
background.layer.cornerRadius = 8
background.clipsToBounds = true
background.addSubview(label)
NSLayoutConstraint.activate([
background.centerXAnchor.constraint(equalTo: label.centerXAnchor),
background.centerYAnchor.constraint(equalTo: label.centerYAnchor),
background.widthAnchor.constraint(equalTo: label.widthAnchor, constant: 16),
background.heightAnchor.constraint(equalTo: label.heightAnchor, constant: 16),
])
return background
}
let stack = UIStackView.init(arrangedSubviews: [UIView()] + tags)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .horizontal
stack.alignment = .trailing
stack.spacing = 12
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: view.topAnchor),
stack.widthAnchor.constraint(equalTo: view.widthAnchor),
])
view.backgroundColor = .white
}
}
PlaygroundPage.current.liveView = V()

How to set top left and right corner radius with desired drop shadow in UITabbar?

I've spent almost a couple of hours to figure it out. However, it did not happen and finally, I had to come here. Two things are required to be achieved:
Firstly I'd like to have a spontaneous corner radius at the top (which is basically TopRight & TopLeft) of UITabbar.
Secondly, I'd like to have a shadow above those corner radius(shown in below image).
Please have a look at below image
Let me know if anything further required from my side, I'll surely provide that.
Any help will be appreciated.
Edit 1
One more little question arose here along, suppose, Even if, However, we were able to accomplish this, Would Apple review team accept the application?
I'm being little nervous and curious about it.
Q : One more little question arose here along, suppose, Even if, However, we were able to accomplish this, Would Apple review team accept the application?
A: Yes They are accept your app I have Add This Kind Of TabBar.
Create Custom TabBar
HomeTabController
import UIKit
class HomeTabController: UITabBarController
{
var viewCustomeTab : CustomeTabView!
var lastSender : UIButton!
//MARK:- ViewController Methods
override func viewDidLoad()
{
super.viewDidLoad()
UITabBar.appearance().shadowImage = UIImage()
allocateTabItems()
}
//MARK:- Prepare Methods
// Allocate shop controller with tab bar
func allocateTabItems()
{
let vc1 = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "Avc") as? Avc
let item1 = UINavigationController(rootViewController: vc1!)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.viewControllers = [item1]
createTabBar()
}
func createTabBar()
{
viewCustomeTab = CustomeTabView.instanceFromNib()
viewCustomeTab.translatesAutoresizingMaskIntoConstraints = false
viewCustomeTab.call()
self.view.addSubview(viewCustomeTab)
if #available(iOS 11, *)
{
let guide = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([guide.bottomAnchor.constraint(equalToSystemSpacingBelow: viewCustomeTab.bottomAnchor, multiplier: 0), viewCustomeTab.leadingAnchor.constraint(equalToSystemSpacingAfter: guide.leadingAnchor, multiplier: 0), guide.trailingAnchor.constraint(equalToSystemSpacingAfter: viewCustomeTab.trailingAnchor, multiplier: 0), viewCustomeTab.heightAnchor.constraint(equalToConstant: 70) ])
}
else
{
let standardSpacing: CGFloat = 0
NSLayoutConstraint.activate([viewCustomeTab.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: standardSpacing), bottomLayoutGuide.topAnchor.constraint(equalTo: viewCustomeTab.bottomAnchor, constant: standardSpacing)
])
}
viewCustomeTab.btnTab1.addTarget(self, action: #selector(HomeTabController.buttonTabClickAction(sender:)), for: .touchUpInside)
viewCustomeTab.btnTab2.addTarget(self, action: #selector(HomeTabController.buttonTabClickAction(sender:)), for: .touchUpInside)
viewCustomeTab.btnTab3.addTarget(self, action: #selector(HomeTabController.buttonTabClickAction(sender:)), for: .touchUpInside)
viewCustomeTab.btnTab4.addTarget(self, action: #selector(HomeTabController.buttonTabClickAction(sender:)), for: .touchUpInside)
viewCustomeTab.btnTab5.addTarget(self, action: #selector(HomeTabController.buttonTabClickAction(sender:)), for: .touchUpInside)
//self.view.layoutIfNeeded()
viewCustomeTab.layoutIfNeeded()
viewCustomeTab.btnTab1.alignContentVerticallyByCenter(offset: 3)
viewCustomeTab.btnTab2.alignContentVerticallyByCenter(offset: 3)
viewCustomeTab.btnTab3.alignContentVerticallyByCenter(offset: 3)
viewCustomeTab.btnTab4.alignContentVerticallyByCenter(offset: 3)
viewCustomeTab.btnTab5.alignContentVerticallyByCenter(offset: 3)
viewCustomeTab.btnTab1.isSelected = true
}
//MARK:- Button Click Actions
//Manage Tab From Here
func setSelect(sender:UIButton)
{
viewCustomeTab.btnTab1.isSelected = false
viewCustomeTab.btnTab2.isSelected = false
viewCustomeTab.btnTab3.isSelected = false
viewCustomeTab.btnTab4.isSelected = false
viewCustomeTab.btnTab5.isSelected = false
sender.isSelected = true
}
#objc func buttonTabClickAction(sender:UIButton)
{
//self.selectedIndex = sender.tag
if sender.tag == 0
{
let vc1 = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "Bvc") as? Bvc
let item1 = UINavigationController(rootViewController: vc1!)
item1.navigationBar.isHidden = false
self.viewControllers = [item1]
setSelect(sender: viewCustomeTab.btnTab1)
return
}
if sender.tag == 1
{
let vc2 = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "Cvc") as? Cvc
let item2 = UINavigationController(rootViewController: vc2!)
item2.navigationBar.isHidden = false
item2.navigationBar.isTranslucent = false
self.viewControllers = [item2]
setSelect(sender: viewCustomeTab.btnTab2)
return
}
if sender.tag == 2
{
let vc3 = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "Dvc") as? Dvc
let item3 = UINavigationController(rootViewController: vc3!)
item3.navigationBar.isHidden = false
item3.navigationBar.isTranslucent = false
self.viewControllers = [item3]
setSelect(sender: viewCustomeTab.btnTab3)
return
}
if sender.tag == 3
{
}
if sender.tag == 4
{
}
}
}
Create Custom View For Shadow Effect and For + Button.
import UIKit
class CustomeTabView: UIView
{
#IBOutlet weak var btnTab5: UIButton!
#IBOutlet weak var btnTab4: UIButton!
#IBOutlet weak var btnTab3: UIButton!
#IBOutlet weak var btnTab2: UIButton!
#IBOutlet weak var btnTab1: UIButton!
#IBOutlet weak var vRadius: UIView!
class func instanceFromNib() -> CustomeTabView
{
return UINib(nibName: "CustomeTabView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CustomeTabView
}
private var shadowLayer: CAShapeLayer!
override func layoutSubviews()
{
super.layoutSubviews()
let shadowSize : CGFloat = 2.0
let shadowPath = UIBezierPath(roundedRect: CGRect(x: -shadowSize / 2, y: -shadowSize / 2, width: self.vRadius.frame.size.width, height: self.vRadius.frame.size.height), cornerRadius : 20)
self.vRadius.layer.masksToBounds = false
self.vRadius.layer.shadowColor = UIColor.black.cgColor
self.vRadius.layer.shadowOffset = CGSize.zero//(width: self.vRadius.frame.size.width, height: self.vRadius.frame.size.height)
self.vRadius.layer.shadowOpacity = 0.5
self.vRadius.layer.shadowPath = shadowPath.cgPath
self.vRadius.layer.cornerRadius = 20
}
OpenImg
Swift 4.2
You can achieve this with some custom view with a custom tab bar controller. You can customize the colors and shadows by editing only the custom views.
Custom Tab Bar Controller
import UIKit
class MainTabBarController: UITabBarController{
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tabBar.backgroundImage = UIImage.from(color: .clear)
tabBar.shadowImage = UIImage()
let tabbarBackgroundView = RoundShadowView(frame: tabBar.frame)
tabbarBackgroundView.cornerRadius = 25
tabbarBackgroundView.backgroundColor = .white
tabbarBackgroundView.frame = tabBar.frame
view.addSubview(tabbarBackgroundView)
let fillerView = UIView()
fillerView.frame = tabBar.frame
fillerView.roundCorners([.topLeft, .topRight], radius: 25)
fillerView.backgroundColor = .white
view.addSubview(fillerView)
view.bringSubviewToFront(tabBar)
}
Rounded Shadow View
import UIKit
class RoundShadowView: UIView {
let containerView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
layoutView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layoutView() {
// set the shadow of the view's layer
layer.backgroundColor = UIColor.clear.cgColor
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: -8.0)
layer.shadowOpacity = 0.12
layer.shadowRadius = 10.0
containerView.layer.cornerRadius = cornerRadius
containerView.layer.masksToBounds = true
addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
// pin the containerView to the edges to the view
containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
UIImage extension
import UIKit
extension UIImage {
static func from(color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
To add any radius or shape you can use a UIBezierPath. The example that I put has left and right corners with a radius and you can use more designable personalizations if you want.
#IBDesignable class TabBarWithCorners: UITabBar {
#IBInspectable var color: UIColor?
#IBInspectable var radii: CGFLoat = 15.0
private var shapeLayer: CALayer?
override func draw(_ rect: CGRect) {
addShape()
}
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.gray.withAlphaComponent(0.1).cgColor
shapeLayer.fillColor = color?.cgColor ?? UIColor.white.cgColor
shapeLayer.lineWidth = 1
if let oldShapeLayer = self.shapeLayer {
layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
private func createPath() -> CGPath {
let path = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: radii, height: 0.0))
return path.cgPath
}
}
Swift 5.3.1, XCode 11+, iOS 14
For using in storyboards:
import UIKit
class CustomTabBar: UITabBar {
override func awakeFromNib() {
super.awakeFromNib()
layer.masksToBounds = true
layer.cornerRadius = 20
layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
}
}
Subclassing UITabBarController then overried viewWillLayoutSubviews()
and add this code .
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.tabBar.layer.masksToBounds = true
self.tabBar.layer.cornerRadius = 12 // whatever you want
self.tabBar.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner] // only the top right and left corners
}
This will be the result

How do I prevent one UIView from being hidden by another UIView?

I'm creating a custom, reusable segmented controller using UIViews and I'm having a problem with overlapping views. It currently looks like this:
You can see that the blue selector is under the buttons but I want it to sit at the bottom and be four pixels high. To do this, I have:
let numberOfButtons = CGFloat(buttonTitles.count)
let selectorWidth = frame.width / numberOfButtons
let selectorYPosition = frame.height - 3 <--- This cause it to be hidden behind the button
selector = UIView(frame: CGRect(x: 0, y: selectorYPosition, width: selectorWidth, height: 4))
selector.layer.cornerRadius = 0
selector.backgroundColor = selectorColor
addSubview(selector)
bringSubviewToFront(selector) <--- I thought this would work but it does nothing
which results in the selector UIView being hidden behind the segment UIView (I have the Y position set to - 3 so you can see how it's being covered up. I actually want it to be - 4, but that makes it disappear entirely):
I thought using bringSubviewToFront() would bring it in front of the segment UIView but it doesn't seem to do anything. I've looked through Apple View Programming Guide and lots of SO threads but can't find an answer.
Can anybody help me see what I'm missing?
Full code:
class CustomSegmentedControl: UIControl {
var buttons = [UIButton]()
var selector: UIView!
var selectedButtonIndex = 0
var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
var borderColor: UIColor = UIColor.black {
didSet {
layer.borderColor = borderColor.cgColor
}
}
var separatorBorderColor: UIColor = UIColor.lightGray {
didSet {
}
}
var commaSeparatedTitles: String = "" {
didSet {
updateView()
}
}
var textColor: UIColor = .lightGray {
didSet {
updateView()
}
}
var selectorColor: UIColor = .blue {
didSet {
updateView()
}
}
var selectorTextColor: UIColor = .black {
didSet {
updateView()
}
}
func updateView() {
buttons.removeAll()
subviews.forEach { $0.removeFromSuperview() }
// create buttons
let buttonTitles = commaSeparatedTitles.components(separatedBy: ",")
for buttonTitle in buttonTitles {
let button = UIButton(type: .system)
button.setTitle(buttonTitle, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.backgroundColor = UIColor.white
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
buttons.append(button)
}
// make first button selected
buttons[0].setTitleColor(selectorTextColor, for: .normal)
let numberOfButtons = CGFloat(buttonTitles.count)
let selectorWidth = frame.width / numberOfButtons
let selectorYPosition = frame.height - 3
selector = UIView(frame: CGRect(x: 0, y: selectorYPosition, width: selectorWidth, height: 4))
selector.layer.cornerRadius = 0
selector.backgroundColor = selectorColor
addSubview(selector)
bringSubviewToFront(selector)
let stackView = UIStackView(arrangedSubviews: buttons)
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fillEqually
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
#objc func buttonTapped(button: UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == button {
let numberOfButtons = CGFloat(buttons.count)
let selectorStartPosition = frame.width / numberOfButtons * CGFloat(buttonIndex)
UIView.animate(withDuration: 0.3, animations: { self.selector.frame.origin.x = selectorStartPosition })
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
sendActions(for: .valueChanged)
}
}
You are covering up your selector with the stackView.
You need to do:
bringSubviewToFront(selector)
after you have added all of the views. Move that line to the bottom of updateView().