From 722b3ccc0ba70dafc95b66c8e9a2472a6e840f89 Mon Sep 17 00:00:00 2001 From: yysskk Date: Tue, 22 May 2018 18:39:10 +0900 Subject: [PATCH 1/4] Add tabView delegate --- Sources/SwipeMenuView.swift | 55 ++++++----------------- Sources/TabView.swift | 87 +++++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 51 deletions(-) diff --git a/Sources/SwipeMenuView.swift b/Sources/SwipeMenuView.swift index 49a8ffa..dc08339 100644 --- a/Sources/SwipeMenuView.swift +++ b/Sources/SwipeMenuView.swift @@ -148,10 +148,10 @@ public protocol SwipeMenuViewDataSource: class { /// Return the number of pages in `SwipeMenuView`. func numberOfPages(in swipeMenuView: SwipeMenuView) -> Int - /// Return strings to be displayed at the specified tag in `SwipeMenuView`. + /// Return strings to be displayed at the tab in `SwipeMenuView`. func swipeMenuView(_ swipeMenuView: SwipeMenuView, titleForPageAt index: Int) -> String - /// Return a ViewController to be displayed at the specified page in `SwipeMenuView`. + /// Return a ViewController to be displayed at the page in `SwipeMenuView`. func swipeMenuView(_ swipeMenuView: SwipeMenuView, viewControllerForPageAt index: Int) -> UIViewController } @@ -169,6 +169,7 @@ open class SwipeMenuView: UIView { didSet { guard let tabView = tabView else { return } tabView.dataSource = self + tabView.tabViewDelegate = self addSubview(tabView) layout(tabView: tabView) } @@ -294,7 +295,6 @@ open class SwipeMenuView: UIView { tabView = TabView(frame: CGRect(x: 0, y: 0, width: frame.width, height: options.tabView.height), options: options.tabView) tabView?.clipsToBounds = options.tabView.clipsToBounds - addTabItemGestures() contentScrollView = ContentScrollView(frame: CGRect(x: 0, y: options.tabView.height, width: frame.width, height: frame.height - options.tabView.height), default: defaultIndex, options: options.contentScrollView) contentScrollView?.clipsToBounds = options.contentScrollView.clipsToBounds @@ -345,58 +345,29 @@ open class SwipeMenuView: UIView { } } -// MARK: - TabViewDataSource +// MARK: - TabViewDelegate, TabViewDataSource -extension SwipeMenuView: TabViewDataSource { +extension SwipeMenuView: TabViewDelegate, TabViewDataSource { - public func numberOfItems(in menuView: TabView) -> Int { - return dataSource?.numberOfPages(in: self) ?? 0 - } - - public func tabView(_ tabView: TabView, titleForItemAt index: Int) -> String? { - return dataSource?.swipeMenuView(self, titleForPageAt: index) - } -} - -// MARK: - GestureRecognizer - -extension SwipeMenuView { - - fileprivate var tapGestureRecognizer: UITapGestureRecognizer { - let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapItemView(_:))) - gestureRecognizer.numberOfTapsRequired = 1 - gestureRecognizer.cancelsTouchesInView = false - return gestureRecognizer - } - - fileprivate func addTabItemGestures() { - tabView?.itemViews.forEach { - $0.addGestureRecognizer(tapGestureRecognizer) - } - } - - @objc func tapItemView(_ recognizer: UITapGestureRecognizer) { + public func tabView(_ tabView: TabView, didSelectTabAt index: Int) { - guard let itemView = recognizer.view as? TabItemView, let tabView = tabView, let index: Int = tabView.itemViews.index(of: itemView), let contentScrollView = contentScrollView else { return } - if currentIndex == index { return } + guard let contentScrollView = contentScrollView, + currentIndex != index else { return } isJumping = true jumpingToIndex = index contentScrollView.jump(to: index, animated: true) - moveTabItem(tabView: tabView, index: index) update(from: currentIndex, to: index) } - private func moveTabItem(tabView: TabView, index: Int) { + public func numberOfItems(in menuView: TabView) -> Int { + return dataSource?.numberOfPages(in: self) ?? 0 + } - switch options.tabView.addition { - case .underline: - tabView.animateUnderlineView(index: index, completion: nil) - case .none: - tabView.update(index) - } + public func tabView(_ tabView: TabView, titleForItemAt index: Int) -> String? { + return dataSource?.swipeMenuView(self, titleForPageAt: index) } } diff --git a/Sources/TabView.swift b/Sources/TabView.swift index be993dc..355142b 100644 --- a/Sources/TabView.swift +++ b/Sources/TabView.swift @@ -1,15 +1,37 @@ import UIKit -public protocol TabViewDataSource { +// MARK: - TabViewDelegate +public protocol TabViewDelegate: class { + + /// Called before selecting the tab. + func tabView(_ tabView: TabView, willSelectTabAt index: Int) + + /// Called after selecting the tab. + func tabView(_ tabView: TabView, didSelectTabAt index: Int) +} + +extension TabViewDelegate { + public func tabView(_ tabView: TabView, willSelectTabAt index: Int) {} + + public func tabView(_ tabView: TabView, didSelectTabAt index: Int) {} +} + +// MARK: - TabViewDataSource + +public protocol TabViewDataSource: class { + + /// Return the number of Items in `TabView`. func numberOfItems(in tabView: TabView) -> Int + /// Return strings to be displayed at the tab in `TabView`. func tabView(_ tabView: TabView, titleForItemAt index: Int) -> String? } open class TabView: UIScrollView { - open var dataSource: TabViewDataSource! + open weak var tabViewDelegate: TabViewDelegate? + open weak var dataSource: TabViewDataSource? var itemViews: [TabItemView] = [] @@ -19,7 +41,7 @@ open class TabView: UIScrollView { fileprivate var currentIndex: Int = 0 - fileprivate var options: SwipeMenuViewOptions.TabView = SwipeMenuViewOptions.TabView() + fileprivate(set) var options: SwipeMenuViewOptions.TabView = SwipeMenuViewOptions.TabView() private var leftMarginConstraint: NSLayoutConstraint = .init() private var widthConstraint: NSLayoutConstraint = .init() @@ -73,6 +95,10 @@ open class TabView: UIScrollView { updateSelectedItem(by: currentIndex) } + public func set(_ options: SwipeMenuViewOptions.TabView) { + self.options = options + } + fileprivate func focus(on target: UIView, animated: Bool = true) { if options.style == .segmented { return } @@ -103,11 +129,12 @@ open class TabView: UIScrollView { reset() - guard dataSource.numberOfItems(in: self) > 0 else { return } + guard let dataSource = dataSource, + dataSource.numberOfItems(in: self) > 0 else { return } setupScrollView() - setupContainerView() - setupTabItemViews() + setupContainerView(dataSource: dataSource) + setupTabItemViews(dataSource: dataSource) setupUnderlineView() } @@ -123,7 +150,7 @@ open class TabView: UIScrollView { translatesAutoresizingMaskIntoConstraints = false } - fileprivate func setupContainerView() { + fileprivate func setupContainerView(dataSource: TabViewDataSource) { containerView.alignment = .leading @@ -168,7 +195,7 @@ open class TabView: UIScrollView { addSubview(containerView) } - fileprivate func setupTabItemViews() { + fileprivate func setupTabItemViews(dataSource: TabViewDataSource) { itemViews = [] @@ -232,6 +259,7 @@ open class TabView: UIScrollView { } layout(containerView: containerView, containerWidth: xPosition) + addTabItemGestures() animateUnderlineView(index: currentIndex) } @@ -320,7 +348,9 @@ extension TabView { } fileprivate func resetUnderlineViewPosition(index: Int) { - guard options.style == .segmented, dataSource.numberOfItems(in: self) > 0 else { return } + guard options.style == .segmented, + let dataSource = dataSource, + dataSource.numberOfItems(in: self) > 0 else { return } let adjustCellWidth: CGFloat if #available(iOS 11.0, *), options.isSafeAreaEnabled && safeAreaInsets != .zero { adjustCellWidth = (frame.width - options.margin * 2 - safeAreaInsets.left - safeAreaInsets.right) / CGFloat(dataSource.numberOfItems(in: self)) - options.underlineView.margin * 2 @@ -413,3 +443,42 @@ extension TabView { focus(on: currentItem, animated: false) } } + +// MARK: - GestureRecognizer + +extension TabView { + + fileprivate var tapGestureRecognizer: UITapGestureRecognizer { + let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapItemView(_:))) + gestureRecognizer.numberOfTapsRequired = 1 + gestureRecognizer.cancelsTouchesInView = false + return gestureRecognizer + } + + fileprivate func addTabItemGestures() { + itemViews.forEach { + $0.addGestureRecognizer(tapGestureRecognizer) + } + } + + @objc func tapItemView(_ recognizer: UITapGestureRecognizer) { + guard let itemView = recognizer.view as? TabItemView, + let index: Int = itemViews.index(of: itemView), + currentIndex != index else { return } + tabViewDelegate?.tabView(self, willSelectTabAt: index) + moveTabItem(index: index) + update(index) + tabViewDelegate?.tabView(self, didSelectTabAt: index) + } + + private func moveTabItem(index: Int) { + + switch options.addition { + case .underline: + animateUnderlineView(index: index, completion: nil) + case .none: + update(index) + } + } +} + From 9fab531e3aeb821d4511c67b8c9b7d95b9021d9f Mon Sep 17 00:00:00 2001 From: yysskk Date: Tue, 22 May 2018 20:04:35 +0900 Subject: [PATCH 2/4] Update README --- README.md | 24 ++++++++++++++++++++---- Sources/TabView.swift | 10 ++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aca691b..05053df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -

- -

+# SwipeMenuViewController [![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat )](https://developer.apple.com/iphone/index.action) @@ -11,7 +9,6 @@ [![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat )](http://mit-license.org) - ## Overview SwipeMenuViewController provides `SwipeMenuView` and `SwipeMenuViewController`. This is very useful to build swipe-based paging UI. @@ -255,6 +252,15 @@ func jump(to index: Int, animated: Bool) func willChangeOrientation() ``` +`TabView` has the following methods. +```swift + +/// Reloads all `TabView` item views with the dataSource and refreshes the display. +public func reload() + +/// Set `TabView` options +public func set(_ options: SwipeMenuViewOptions.TabView) {} +``` ### Protocols `SwipeMenuViewDataSource` and `SwipeMenuViewDelegate` has the following methods. @@ -296,6 +302,16 @@ open weak var dataSource: SwipeMenuDataSource! private(set) var currentIndex ``` +`TabView` has the following properties. +```swift + +// An object conforms `TabViewDelegate`. Provide views to populate the `TabView`. +open weak var tabViewDelegate: TabViewDelegate? + +// An object conforms `TabViewDataSource`. Provide views and respond to `TabView` events. +open weak var dataSource: TabViewDataSource? +``` + ### Customization `SwipeMenuView` is customizable by designated options property when calling `reloadData()` method. Here are many properties of `SwipeMenuViewOptions` which you are able to customize it for your needs. diff --git a/Sources/TabView.swift b/Sources/TabView.swift index 355142b..4306601 100644 --- a/Sources/TabView.swift +++ b/Sources/TabView.swift @@ -59,7 +59,7 @@ open class TabView: UIScrollView { } open override func didMoveToSuperview() { - setup() + reload() } open override func layoutSubviews() { @@ -79,7 +79,7 @@ open class TabView: UIScrollView { layoutIfNeeded() } - public func reset() { + func reset() { itemViews.forEach { $0.removeFromSuperview() } underlineView.removeFromSuperview() @@ -87,7 +87,7 @@ open class TabView: UIScrollView { itemViews = [] } - public func update(_ index: Int) { + func update(_ index: Int) { if currentIndex == index { return } @@ -95,6 +95,7 @@ open class TabView: UIScrollView { updateSelectedItem(by: currentIndex) } + /// Set TabView options public func set(_ options: SwipeMenuViewOptions.TabView) { self.options = options } @@ -125,7 +126,8 @@ open class TabView: UIScrollView { // MARK: - Setup - fileprivate func setup() { + /// Reloads all `TabView` item views with the dataSource and refreshes the display. + public func reload() { reset() From 1e150eb857a33ad6cd87e6d36e0ed79d88e4bb09 Mon Sep 17 00:00:00 2001 From: yysskk Date: Tue, 29 May 2018 17:57:53 +0900 Subject: [PATCH 3/4] Fix reload to reloadData --- README.md | 5 +---- Sources/TabView.swift | 13 +++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 05053df..7a8f75b 100644 --- a/README.md +++ b/README.md @@ -256,10 +256,7 @@ func willChangeOrientation() ```swift /// Reloads all `TabView` item views with the dataSource and refreshes the display. -public func reload() - -/// Set `TabView` options -public func set(_ options: SwipeMenuViewOptions.TabView) {} +public func reloadData(options: SwipeMenuViewOptions.TabView? = nil, default defaultIndex: Int? = nil) ``` ### Protocols diff --git a/Sources/TabView.swift b/Sources/TabView.swift index 4306601..13a1c8c 100644 --- a/Sources/TabView.swift +++ b/Sources/TabView.swift @@ -59,7 +59,7 @@ open class TabView: UIScrollView { } open override func didMoveToSuperview() { - reload() + reloadData() } open override func layoutSubviews() { @@ -127,7 +127,12 @@ open class TabView: UIScrollView { // MARK: - Setup /// Reloads all `TabView` item views with the dataSource and refreshes the display. - public func reload() { + public func reloadData(options: SwipeMenuViewOptions.TabView? = nil, + default defaultIndex: Int? = nil) { + + if let options = options { + self.options = options + } reset() @@ -138,6 +143,10 @@ open class TabView: UIScrollView { setupContainerView(dataSource: dataSource) setupTabItemViews(dataSource: dataSource) setupUnderlineView() + + if let defaultIndex = defaultIndex { + updateSelectedItem(by: defaultIndex) + } } fileprivate func setupScrollView() { From 882c2f12d8dd512f4ca8d397d7afc79e65598581 Mon Sep 17 00:00:00 2001 From: yysskk Date: Wed, 30 May 2018 15:34:08 +0900 Subject: [PATCH 4/4] Refactoring --- Sources/SwipeMenuView.swift | 2 ++ Sources/TabView.swift | 37 ++++++++++++++++--------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Sources/SwipeMenuView.swift b/Sources/SwipeMenuView.swift index dc08339..39e42a8 100644 --- a/Sources/SwipeMenuView.swift +++ b/Sources/SwipeMenuView.swift @@ -83,6 +83,8 @@ public struct SwipeMenuViewOptions { /// UnderlineView options public var underlineView = UndelineView() + + public init() { } } public struct ContentScrollView { diff --git a/Sources/TabView.swift b/Sources/TabView.swift index 13a1c8c..1ad2f4d 100644 --- a/Sources/TabView.swift +++ b/Sources/TabView.swift @@ -79,27 +79,6 @@ open class TabView: UIScrollView { layoutIfNeeded() } - func reset() { - - itemViews.forEach { $0.removeFromSuperview() } - underlineView.removeFromSuperview() - containerView.removeFromSuperview() - itemViews = [] - } - - func update(_ index: Int) { - - if currentIndex == index { return } - - currentIndex = index - updateSelectedItem(by: currentIndex) - } - - /// Set TabView options - public func set(_ options: SwipeMenuViewOptions.TabView) { - self.options = options - } - fileprivate func focus(on target: UIView, animated: Bool = true) { if options.style == .segmented { return } @@ -149,6 +128,22 @@ open class TabView: UIScrollView { } } + func reset() { + + itemViews.forEach { $0.removeFromSuperview() } + underlineView.removeFromSuperview() + containerView.removeFromSuperview() + itemViews = [] + } + + func update(_ index: Int) { + + if currentIndex == index { return } + + currentIndex = index + updateSelectedItem(by: currentIndex) + } + fileprivate func setupScrollView() { backgroundColor = options.backgroundColor showsHorizontalScrollIndicator = false