Skip to content

Commit

Permalink
Issues resolved, more dynamic and useful (#27)
Browse files Browse the repository at this point in the history
+ Can change tab position programatically
+ Circle now dynamic
+ Support up to iOS 14.5
+ Performance improved

Issues #7, #13, #14, #16, #18, #19, #25, #26 has been resolved.
  • Loading branch information
kamrul-cse authored Jul 18, 2021
1 parent 4fc93de commit b5a114a
Show file tree
Hide file tree
Showing 17 changed files with 598 additions and 479 deletions.
80 changes: 51 additions & 29 deletions CircleBar/Classes/SHCircleBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,43 @@
import UIKit

@IBDesignable class SHCircleBar: UITabBar {
var tabWidth: CGFloat = 0
var index: CGFloat = 0 {
private var tabWidth: CGFloat = 0
private var index: CGFloat = 0 {
willSet{
self.previousIndex = index
}
}
private var animated = false
private var selectedImage: UIImage?
private var previousIndex: CGFloat = 0

override init(frame: CGRect) {
super.init(frame: frame)
customInit()
}

required init?(coder aDecoder: NSCoder) {
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
customInit()

}
override func draw(_ rect: CGRect) {

open override func draw(_ rect: CGRect) {
drawCurve()
}
}

extension SHCircleBar {

func select(itemAt: Int, animated: Bool) {
self.index = CGFloat(itemAt)
self.animated = animated
self.selectedImage = self.selectedItem?.selectedImage
self.selectedItem?.selectedImage = nil
self.setNeedsDisplay()
}

private func drawCurve() {
let fillColor: UIColor = .white
tabWidth = self.bounds.width / CGFloat(self.items!.count)
let bezPath = drawPath(for: index)
Expand All @@ -50,40 +67,45 @@ import UIKit
mask.add(bezAnimation, forKey: nil)
}
self.layer.mask = mask

}

func select(itemAt: Int, animated: Bool) {
self.index = CGFloat(itemAt)
self.animated = animated
self.selectedImage = self.selectedItem?.selectedImage
self.selectedItem?.selectedImage = nil
self.setNeedsDisplay()
}

func customInit(){
private func customInit(){
self.tintColor = .white
self.barTintColor = .white
self.backgroundColor = .white
}

private func drawPath(for index: CGFloat) -> UIBezierPath {
let bezPath = UIBezierPath()

let firstPoint = CGPoint(x: (index * tabWidth) - 25, y: 0)
let firstPointFirstCurve = CGPoint(x: ((tabWidth * index) + tabWidth / 4), y: 0)
let firstPointSecondCurve = CGPoint(x: ((index * tabWidth) - 25) + tabWidth / 8, y: 52)

let middlePoint = CGPoint(x: (tabWidth * index) + tabWidth / 2, y: 55)
let middlePointFirstCurve = CGPoint(x: (((tabWidth * index) + tabWidth) - tabWidth / 8) + 25, y: 52)
let middlePointSecondCurve = CGPoint(x: (((tabWidth * index) + tabWidth) - tabWidth / 4), y: 0)

let lastPoint = CGPoint(x: (tabWidth * index) + tabWidth + 25, y: 0)
bezPath.move(to: firstPoint)
bezPath.addCurve(to: middlePoint, controlPoint1: firstPointFirstCurve, controlPoint2: firstPointSecondCurve)
bezPath.addCurve(to: lastPoint, controlPoint1: middlePointFirstCurve, controlPoint2: middlePointSecondCurve)

let tabHeight: CGFloat = tabWidth

let leftPoint = CGPoint(x: (index * tabWidth), y: 0)
let leftPointCurveUp = CGPoint(
x: ((tabWidth * index) + tabWidth / 5),
y: 0)
let leftPointCurveDown = CGPoint(
x: ((index * tabWidth) - tabWidth*0.2) + tabWidth / 4,
y: tabHeight*0.40)

let middlePoint = CGPoint(
x: (tabWidth * index) + tabWidth / 2,
y: tabHeight*0.4)
let middlePointCurveDown = CGPoint(
x: (((index * tabWidth) - tabWidth*0.2) + tabWidth / 10) + tabWidth,
y: tabHeight*0.40)
let middlePointCurveUp = CGPoint(
x: (((tabWidth * index) + tabWidth) - tabWidth / 5),
y: 0)

let rightPoint = CGPoint(x: (tabWidth * index) + tabWidth, y: 0)
bezPath.move(to: leftPoint)
bezPath.addCurve(to: middlePoint, controlPoint1: leftPointCurveUp, controlPoint2: leftPointCurveDown)
bezPath.addCurve(to: rightPoint, controlPoint1: middlePointCurveDown, controlPoint2: middlePointCurveUp)

bezPath.append(UIBezierPath(rect: self.bounds))

return bezPath
}


}

185 changes: 108 additions & 77 deletions CircleBar/Classes/SHCircleBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,82 +10,52 @@ import UIKit

class SHCircleBarController: UITabBarController {

fileprivate var shouldSelectOnTabBar = true
private var circleView : UIView!
private var circleImageView: UIImageView!
open override var selectedViewController: UIViewController? {

public override var selectedViewController: UIViewController? {
willSet {
guard shouldSelectOnTabBar, let newValue = newValue else {
shouldSelectOnTabBar = true
return
}
guard let tabBar = tabBar as? SHCircleBar, let index = viewControllers?.index(of: newValue) else {return}
guard let newValue = newValue,
let tabBar = tabBar as? SHCircleBar,
let index = viewControllers?.firstIndex(of: newValue)
else { return }
updateCircle(index: index)
tabBar.select(itemAt: index, animated: true)
}
}

open override var selectedIndex: Int {
public override var selectedIndex: Int {
willSet {
guard shouldSelectOnTabBar else {
shouldSelectOnTabBar = true
return
}
guard let tabBar = tabBar as? SHCircleBar else {
return
}
tabBar.select(itemAt: selectedIndex, animated: true)
guard let tabBar = tabBar as? SHCircleBar else { return }
updateCircle(index: newValue)
tabBar.select(itemAt: newValue, animated: true)
}
}

open override func viewDidLoad() {
super.viewDidLoad()
let tabBar = SHCircleBar()
self.setValue(tabBar, forKey: "tabBar")

self.circleView = UIView(frame: .zero)
circleView.layer.cornerRadius = 30
circleView.backgroundColor = .white
circleView.isUserInteractionEnabled = false

self.circleImageView = UIImageView(frame: .zero)
circleImageView.layer.cornerRadius = 30
circleImageView.isUserInteractionEnabled = false
circleImageView.contentMode = .center

circleView.addSubview(circleImageView)
self.view.addSubview(circleView)
let tabWidth = self.view.bounds.width / CGFloat(self.tabBar.items?.count ?? 4)

circleView.frame = CGRect(x: tabWidth / 2 - 30, y: self.tabBar.frame.origin.y - 40, width: 60, height: 60)
circleImageView.frame = self.circleView.bounds
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
circleImageView.image = image(with: self.tabBar.selectedItem?.image ?? self.tabBar.items?.first?.image, scaledTo: CGSize(width: 30, height: 30))

}

private var _barHeight: CGFloat = 74
open var barHeight: CGFloat {
public var barHeight: CGFloat {
get {
if #available(iOS 11.0, *) {
return _barHeight + view.safeAreaInsets.bottom
} else {
return _barHeight
}
} else { return _barHeight }
}
set {
_barHeight = newValue
updateTabBarFrame()
}
}

private func updateTabBarFrame() {
var tabFrame = self.tabBar.frame
tabFrame.size.height = barHeight
tabFrame.origin.y = self.view.frame.size.height - barHeight
self.tabBar.frame = tabFrame
tabBar.setNeedsLayout()
open override func viewDidLoad() {
super.viewDidLoad()
let tabBar = SHCircleBar()
self.setValue(tabBar, forKey: "tabBar")

addCirleView()
}

open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
circleImageView.image = self.tabBar.selectedItem?.image ?? self.tabBar.items?.first?.image
}

open override func viewWillLayoutSubviews() {
Expand All @@ -101,31 +71,92 @@ class SHCircleBarController: UITabBarController {
}

open override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
guard let idx = tabBar.items?.index(of: item) else { return }
if idx != selectedIndex, let controller = viewControllers?[idx] {
shouldSelectOnTabBar = false
selectedIndex = idx
let tabWidth = self.view.bounds.width / CGFloat(self.tabBar.items!.count)
UIView.animate(withDuration: 0.3) {
self.circleView.frame = CGRect(x: (tabWidth * CGFloat(idx) + tabWidth / 2 - 30), y: self.tabBar.frame.origin.y - 15, width: 60, height: 60)
}
UIView.animate(withDuration: 0.15, animations: {
self.circleImageView.alpha = 0
}) { (_) in
self.circleImageView.image = self.image(with: item.image, scaledTo: CGSize(width: 30, height: 30))
UIView.animate(withDuration: 0.15, animations: {
self.circleImageView.alpha = 1
})
}
delegate?.tabBarController?(self, didSelect: controller)
guard let index = tabBar.items?.firstIndex(of: item) else { return }
updateCircle(index: index)
}

}

extension SHCircleBarController {
public func updateCircle(index: Int) {
guard let items = tabBar.items,
let vcs = viewControllers,
index < items.count,
index < vcs.count,
index != selectedIndex else { return }

let item = items[index]
let controller = vcs[index]

let tabWidth = self.view.bounds.width / CGFloat(items.count)
let circleWidth = self.circleView.bounds.width

UIView.animate(withDuration: 0.3) { [weak self] in
guard let `self` = self else { return }
self.circleView.frame = CGRect(
x: (tabWidth * CGFloat(index) + tabWidth / 2 - circleWidth*0.5),
y: self.circleView.frame.minY,
width: circleWidth,
height: circleWidth)
}

UIView.animate(withDuration: 0.15) { [weak self] in
self?.circleImageView.alpha = 0
} completion: { [weak self] (_) in
self?.circleImageView.image = item.image
UIView.animate(withDuration: 0.15, animations: { [weak self] in
self?.circleImageView.alpha = 1
})
}
delegate?.tabBarController?(self, didSelect: controller)
}
private func image(with image: UIImage?, scaledTo newSize: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(newSize, _: false, _: 0.0)
image?.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let newImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage

fileprivate func updateTabBarFrame() {
var tabFrame = self.tabBar.frame
tabFrame.size.height = barHeight
tabFrame.origin.y = self.view.frame.size.height - barHeight
self.tabBar.frame = tabFrame
tabBar.setNeedsLayout()
}

fileprivate func addCirleView() {
let tabWidth = self.view.bounds.width / CGFloat(self.tabBar.items?.count ?? 4)
let circleViewWidth = tabWidth*0.5
let circleViewRadius = circleViewWidth*0.5

self.circleView = UIView(frame: .zero)
circleView.layer.cornerRadius = circleViewRadius
circleView.backgroundColor = .white

self.circleImageView = UIImageView(frame: .zero)
circleImageView.layer.cornerRadius = circleViewRadius
circleImageView.isUserInteractionEnabled = false
circleImageView.contentMode = .center

circleView.addSubview(circleImageView)
self.view.addSubview(circleView)

circleView.layer.shadowOffset = CGSize(width: 0, height: 0)
circleView.layer.shadowRadius = 2
circleView.layer.shadowColor = UIColor.black.cgColor
circleView.layer.shadowOpacity = 0.15

let bottomPadding = getBottomPadding()

circleView.frame = CGRect(
x: tabWidth / 2 - tabWidth*0.25,
y: self.tabBar.frame.origin.y - bottomPadding - circleViewWidth*0.5,
width: circleViewWidth,
height: circleViewWidth)
circleImageView.frame = self.circleView.bounds
}

fileprivate func getBottomPadding() -> CGFloat {
if #available(iOS 13.0, *) {
let window = UIApplication.shared.windows[0]
return window.safeAreaInsets.bottom
} else if #available(iOS 11.0, *) {
return view.safeAreaInsets.bottom
} else { return 0 }
}
}
4 changes: 0 additions & 4 deletions Example/CircleBar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,11 @@
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CircleBar_Example/Pods-CircleBar_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/CircleBar/CircleBar.framework",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
);
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CircleBar.framework",
);
Expand Down
6 changes: 3 additions & 3 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PODS:
- CircleBar (0.1.0)
- CircleBar (0.8.2)

DEPENDENCIES:
- CircleBar (from `../`)
Expand All @@ -9,8 +9,8 @@ EXTERNAL SOURCES:
:path: "../"

SPEC CHECKSUMS:
CircleBar: b9c26c3833a648ef0bd56102d9e15c2dc050588f
CircleBar: 5b882eb3870de5bbda42bef1b2ec6c5c533497db

PODFILE CHECKSUM: c85bb2d45d7d4fdfeeaa049dfcacccb3a811d08b

COCOAPODS: 1.6.0.beta.2
COCOAPODS: 1.10.1
Loading

0 comments on commit b5a114a

Please sign in to comment.