Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<p align="center">
<img src="https://github.com/yysskk/Assets/blob/master/SwipeMenuViewController/Banner.png" width="800">
</p>
# SwipeMenuViewController

[![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat
)](https://developer.apple.com/iphone/index.action)
Expand All @@ -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.
Expand Down Expand Up @@ -255,6 +252,12 @@ 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 reloadData(options: SwipeMenuViewOptions.TabView? = nil, default defaultIndex: Int? = nil)
```

### Protocols
`SwipeMenuViewDataSource` and `SwipeMenuViewDelegate` has the following methods.
Expand Down Expand Up @@ -296,6 +299,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.
Expand Down
57 changes: 15 additions & 42 deletions Sources/SwipeMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public struct SwipeMenuViewOptions {

/// UnderlineView options
public var underlineView = UndelineView()

public init() { }
}

public struct ContentScrollView {
Expand Down Expand Up @@ -148,10 +150,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
}

Expand All @@ -169,6 +171,7 @@ open class SwipeMenuView: UIView {
didSet {
guard let tabView = tabView else { return }
tabView.dataSource = self
tabView.tabViewDelegate = self
addSubview(tabView)
layout(tabView: tabView)
}
Expand Down Expand Up @@ -294,7 +297,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
Expand Down Expand Up @@ -345,58 +347,29 @@ open class SwipeMenuView: UIView {
}
}

// MARK: - TabViewDataSource

extension SwipeMenuView: 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: - TabViewDelegate, TabViewDataSource

// MARK: - GestureRecognizer
extension SwipeMenuView: TabViewDelegate, TabViewDataSource {

extension SwipeMenuView {
public func tabView(_ tabView: TabView, didSelectTabAt index: Int) {

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

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

Expand Down
129 changes: 102 additions & 27 deletions Sources/TabView.swift
Original file line number Diff line number Diff line change
@@ -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] = []

Expand All @@ -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()
Expand All @@ -37,7 +59,7 @@ open class TabView: UIScrollView {
}

open override func didMoveToSuperview() {
setup()
reloadData()
}

open override func layoutSubviews() {
Expand All @@ -57,22 +79,6 @@ open class TabView: UIScrollView {
layoutIfNeeded()
}

public func reset() {

itemViews.forEach { $0.removeFromSuperview() }
underlineView.removeFromSuperview()
containerView.removeFromSuperview()
itemViews = []
}

public func update(_ index: Int) {

if currentIndex == index { return }

currentIndex = index
updateSelectedItem(by: currentIndex)
}

fileprivate func focus(on target: UIView, animated: Bool = true) {

if options.style == .segmented { return }
Expand All @@ -99,16 +105,43 @@ open class TabView: UIScrollView {

// MARK: - Setup

fileprivate func setup() {
/// Reloads all `TabView` item views with the dataSource and refreshes the display.
public func reloadData(options: SwipeMenuViewOptions.TabView? = nil,
default defaultIndex: Int? = nil) {

if let options = options {
self.options = options
}

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

if let defaultIndex = defaultIndex {
updateSelectedItem(by: defaultIndex)
}
}

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() {
Expand All @@ -123,7 +156,7 @@ open class TabView: UIScrollView {
translatesAutoresizingMaskIntoConstraints = false
}

fileprivate func setupContainerView() {
fileprivate func setupContainerView(dataSource: TabViewDataSource) {

containerView.alignment = .leading

Expand Down Expand Up @@ -168,7 +201,7 @@ open class TabView: UIScrollView {
addSubview(containerView)
}

fileprivate func setupTabItemViews() {
fileprivate func setupTabItemViews(dataSource: TabViewDataSource) {

itemViews = []

Expand Down Expand Up @@ -232,6 +265,7 @@ open class TabView: UIScrollView {
}

layout(containerView: containerView, containerWidth: xPosition)
addTabItemGestures()
animateUnderlineView(index: currentIndex)
}

Expand Down Expand Up @@ -320,7 +354,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
Expand Down Expand Up @@ -413,3 +449,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)
}
}
}