Skip to content

Commit

Permalink
Merge pull request #387 from hyun99999/feature/#351
Browse files Browse the repository at this point in the history
[Feat] #351 - 커스텀 탭바 구현
  • Loading branch information
hyun99999 authored Mar 19, 2022
2 parents 8b66d8d + 54c39c3 commit 0f975e7
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 45 deletions.
50 changes: 45 additions & 5 deletions Spark-iOS/Spark-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@
F80A3E57278C1C2700728E07 /* FeedVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80A3E56278C1C2700728E07 /* FeedVC.swift */; };
F816F122278E1C760008ED00 /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F816F121278E1C760008ED00 /* Login.storyboard */; };
F816F125278E1C920008ED00 /* LoginVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F816F124278E1C920008ED00 /* LoginVC.swift */; };
F821812E27DE224A00DDFBF8 /* SparkTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F821812D27DE224A00DDFBF8 /* SparkTabBar.swift */; };
F821813127DE226300DDFBF8 /* SparkTabBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F821813027DE226300DDFBF8 /* SparkTabBarDelegate.swift */; };
F821813327DE512D00DDFBF8 /* SparkTabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F821813227DE512D00DDFBF8 /* SparkTabBarItem.swift */; };
F821813527DE659E00DDFBF8 /* SparkTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F821813427DE659E00DDFBF8 /* SparkTabBarController.swift */; };
F8250CF127D238440073FFCA /* EditProfileVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8250CF027D238440073FFCA /* EditProfileVC.swift */; };
F8250CF327D238990073FFCA /* EditProfile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F8250CF227D238990073FFCA /* EditProfile.storyboard */; };
F8250CF827D25F240073FFCA /* UserAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8250CF727D25F240073FFCA /* UserAPI.swift */; };
Expand Down Expand Up @@ -365,6 +369,10 @@
F80A3E56278C1C2700728E07 /* FeedVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedVC.swift; sourceTree = "<group>"; };
F816F121278E1C760008ED00 /* Login.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Login.storyboard; sourceTree = "<group>"; };
F816F124278E1C920008ED00 /* LoginVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginVC.swift; sourceTree = "<group>"; };
F821812D27DE224A00DDFBF8 /* SparkTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkTabBar.swift; sourceTree = "<group>"; };
F821813027DE226300DDFBF8 /* SparkTabBarDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkTabBarDelegate.swift; sourceTree = "<group>"; };
F821813227DE512D00DDFBF8 /* SparkTabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkTabBarItem.swift; sourceTree = "<group>"; };
F821813427DE659E00DDFBF8 /* SparkTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkTabBarController.swift; sourceTree = "<group>"; };
F8250CF027D238440073FFCA /* EditProfileVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileVC.swift; sourceTree = "<group>"; };
F8250CF227D238990073FFCA /* EditProfile.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = EditProfile.storyboard; sourceTree = "<group>"; };
F8250CF727D25F240073FFCA /* UserAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAPI.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -739,11 +747,8 @@
F86C68B627955ABA009A5296 /* SparkFlake.swift */,
F8404E8A27AB835F004AEDC3 /* SendSparkButton.swift */,
2B0CF77327BFEDAC003C2E21 /* BottomButton.swift */,
F8D4496B27BDE2D60091F297 /* SparkNavigationBar.swift */,
F8D4496D27BE25610091F297 /* LeftButtonNavigaitonBar.swift */,
F8D4496F27BE2DBC0091F297 /* RightTwoButtonNavigationBar.swift */,
F8D4497127BE46670091F297 /* LeftRightButtonsNavigationBar.swift */,
EB01BC0F27C8DFBA00041A35 /* SparkActionSheet.swift */,
F821812B27DE21FE00DDFBF8 /* SparkNavigationBar */,
F821812C27DE223100DDFBF8 /* SparkTabBar */,
);
path = Classes;
sourceTree = "<group>";
Expand Down Expand Up @@ -1073,6 +1078,7 @@
F8250CFD27D4A55C0073FFCA /* ProfileImageDelegate.swift */,
EBD0D09127DBBBD10007E5AF /* SendThumbnailURLDelegate.swift */,
EBD0D09327DBBC270007E5AF /* SendSparkCellDelegate.swift */,
F821812F27DE225200DDFBF8 /* SparkTabBar */,
);
path = Protocols;
sourceTree = "<group>";
Expand Down Expand Up @@ -1179,6 +1185,36 @@
path = Login;
sourceTree = "<group>";
};
F821812B27DE21FE00DDFBF8 /* SparkNavigationBar */ = {
isa = PBXGroup;
children = (
F8D4496B27BDE2D60091F297 /* SparkNavigationBar.swift */,
F8D4496D27BE25610091F297 /* LeftButtonNavigaitonBar.swift */,
F8D4496F27BE2DBC0091F297 /* RightTwoButtonNavigationBar.swift */,
F8D4497127BE46670091F297 /* LeftRightButtonsNavigationBar.swift */,
EB01BC0F27C8DFBA00041A35 /* SparkActionSheet.swift */,
);
path = SparkNavigationBar;
sourceTree = "<group>";
};
F821812C27DE223100DDFBF8 /* SparkTabBar */ = {
isa = PBXGroup;
children = (
F821812D27DE224A00DDFBF8 /* SparkTabBar.swift */,
F821813227DE512D00DDFBF8 /* SparkTabBarItem.swift */,
F821813427DE659E00DDFBF8 /* SparkTabBarController.swift */,
);
path = SparkTabBar;
sourceTree = "<group>";
};
F821812F27DE225200DDFBF8 /* SparkTabBar */ = {
isa = PBXGroup;
children = (
F821813027DE226300DDFBF8 /* SparkTabBarDelegate.swift */,
);
path = SparkTabBar;
sourceTree = "<group>";
};
F8250CF527D25F000073FFCA /* User */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1543,6 +1579,7 @@
2BBED13327956D170052CA5C /* FeedService.swift in Sources */,
EB26D93827CF74D700C88DCA /* SendSparkCVC.swift in Sources */,
F8D4497227BE46670091F297 /* LeftRightButtonsNavigationBar.swift in Sources */,
F821812E27DE224A00DDFBF8 /* SparkTabBar.swift in Sources */,
2B54D01627D3B2810060DA06 /* FeedReportVC.swift in Sources */,
2BE907E427D9F1BB00B36002 /* ActiveNotice.swift in Sources */,
F8F6D6F7279735BF00725537 /* HabitRoomVC.swift in Sources */,
Expand Down Expand Up @@ -1606,6 +1643,7 @@
EBEA383927953BC700B5736A /* Lottie.swift in Sources */,
F82F57FD2792847D003E4174 /* GenericResponse.swift in Sources */,
F8E4DFD627D0918C00128B1F /* MypageProfileTVC.swift in Sources */,
F821813527DE659E00DDFBF8 /* SparkTabBarController.swift in Sources */,
F80A3E57278C1C2700728E07 /* FeedVC.swift in Sources */,
EB9C50842792A61D00588155 /* HabitAuthVC.swift in Sources */,
2BC17277278F765C00BA3029 /* WaitingFriendCVC.swift in Sources */,
Expand All @@ -1632,6 +1670,7 @@
F8D4496C27BDE2D60091F297 /* SparkNavigationBar.swift in Sources */,
EBD0D09C27DC3A380007E5AF /* OnboardingCVC.swift in Sources */,
2BEA121D27BA1CB400B67756 /* HabitRoomMemberCVC.swift in Sources */,
F821813127DE226300DDFBF8 /* SparkTabBarDelegate.swift in Sources */,
F82F5802279287FD003E4174 /* HabitRoom.swift in Sources */,
F82B2E12278F54CD00219628 /* SplashVC.swift in Sources */,
F8E4DFDA27D0DE3700128B1F /* MypageTableHeaderView.swift in Sources */,
Expand All @@ -1645,6 +1684,7 @@
F8E3496A27969B67001B67E7 /* AuthService.swift in Sources */,
2BA98FB227CB44E800C53820 /* HabitRoomLeaveVC.swift in Sources */,
2BE907E127D9EE9700B36002 /* NoticeService.swift in Sources */,
F821813327DE512D00DDFBF8 /* SparkTabBarItem.swift in Sources */,
F8096F3027841F9200B71D38 /* HomeVC.swift in Sources */,
F8250CFA27D25F2B0073FFCA /* UserService.swift in Sources */,
F82F5808279289B2003E4174 /* HomeAPI.swift in Sources */,
Expand Down
129 changes: 129 additions & 0 deletions Spark-iOS/Spark-iOS/Source/Classes/SparkTabBar/SparkTabBar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// SparkTabBar.swift
// Spark-iOS
//
// Created by kimhyungyu on 2022/03/13.
//

import UIKit

import SnapKit

/// Implement tab bar with UIView.
final class SparkTabBar: UIView {

// MARK: - Properties

// Use for tab bar select delegate.
weak var delegate: SparkTabBarDelegate?

var itemsArray: [UITabBarItem] = []

// Called by the system when the tintColor property changes.
override func tintColorDidChange() {
super.tintColorDidChange()

reloadAppearance()
}

/// Reload appearance of spark tab bar.
func reloadAppearance() {
tabBarItems().forEach { item in
item.selectedColor = tintColor
}
}

/// Use for tab bar.
private lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [])
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.alignment = .center
stackView.spacing = 44.0

return stackView
}()

// MARK: - Initializer

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

setUI()
setLayout()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

/// Set UI.
private func setUI() {
self.backgroundColor = .sparkWhite
}

/// Set layout.
private func setLayout() {
self.addSubview(self.stackView)

self.stackView.snp.makeConstraints {
$0.centerX.equalToSuperview()
$0.top.equalToSuperview().inset(6)
$0.width.equalTo(232)
$0.height.equalTo(48)
}
}

/// Add tab bar item to items array and compose spark tab bar.
func add(items tabBarItems: [UITabBarItem]) {
for tabBarItem in tabBarItems {
self.itemsArray.append(tabBarItem)
self.addItem(with: tabBarItem)
}
}

/// Add spark tab bar item with tab bar item.
///
/// add spark tab bar item to stack view.
private func addItem(with item: UITabBarItem) {
let item = SparkTabBarItem(forItem: item)
item.isUserInteractionEnabled = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(itemTapped(_:)))
item.addGestureRecognizer(tapGestureRecognizer)

// set item's selected color with SparkTabBar's tint color.
item.selectedColor = self.tintColor
self.stackView.addArrangedSubview(item)
}

@objc
private func itemTapped(_ sender: UITapGestureRecognizer) {
if let sparkTabBarItem = sender.view as? SparkTabBarItem,
let index = self.stackView.arrangedSubviews.firstIndex(of: sparkTabBarItem) {
self.select(at: index)
}
}

/// Select item.
///
/// - Parameter notifyDelegate: Value is true when spark tab bar item is selected by tap gesture.
/// If value is always true, delegate methods are called whenever spark tab bar controller's `selectedIndex` value changes.
/// So, corresponding parameter is to solve call loop.
func select(at selectedIndex: Int, notifyDelegate: Bool = true) {
for (index, item) in self.stackView.arrangedSubviews.enumerated() {
if let item = item as? SparkTabBarItem {
item.isSelected = index == selectedIndex ? true : false
}
}

if notifyDelegate {
self.delegate?.sparkTabBar(self, didSelectItemAt: selectedIndex)
}
}

// Return items that compose the tab bar.
private func tabBarItems() -> [SparkTabBarItem] {
// return array containing non-nil results.
return self.stackView.arrangedSubviews.compactMap { $0 as? SparkTabBarItem }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// SparkTabBarController.swift
// Spark-iOS
//
// Created by kimhyungyu on 2022/03/14.
//

import UIKit

import JJFloatingActionButton

class SparkTabBarController: UITabBarController {

// MARK: - Properties

private let bottomSafeArea: CGFloat = 34.0
private let tabBarHeight: CGFloat = 54.0

public let sparkTabBar = SparkTabBar()

override var selectedIndex: Int {
didSet {
self.sparkTabBar.select(at: selectedIndex, notifyDelegate: false)
}
}

override var selectedViewController: UIViewController? {
didSet {
self.sparkTabBar.select(at: selectedIndex, notifyDelegate: false)
}
}

// MARK: - View Life Cycle

override func viewDidLoad() {
super.viewDidLoad()

setUI()
setLayout()
setTabBar()
}
}

// MARK: - Extension

extension SparkTabBarController {

/// Set UI.
private func setUI() {
self.tabBar.isHidden = true
}

/// Set tab bar.
private func setTabBar() {
self.sparkTabBar.select(at: selectedIndex)
self.sparkTabBar.delegate = self
}

// MARK: - Layout

/// Set layout.
private func setLayout() {
self.view.addSubview(sparkTabBar)

sparkTabBar.snp.makeConstraints {
$0.leading.trailing.bottom.equalToSuperview()
$0.height.equalTo(self.tabBarHeight + self.bottomSafeArea)
}
}
}

// MARK: - SparkTabBarDelegate

extension SparkTabBarController: SparkTabBarDelegate {
func sparkTabBar(_ sender: SparkTabBar, didSelectItemAt index: Int) {
self.selectedIndex = index
}
}
Loading

0 comments on commit 0f975e7

Please sign in to comment.