├── .gitignore ├── .swift-version ├── .travis.yml ├── Example ├── .swiftlint.yml ├── Application │ ├── Container.swift │ ├── Delegate │ │ ├── AppDelegate.swift │ │ ├── AppDelegateAssembly.swift │ │ └── Container_AppDelegateAssembly.swift │ └── Info.plist ├── Resources │ └── Assets.xcassets │ │ └── AppIcon.appiconset │ │ └── Contents.json └── UI │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Menu │ ├── Container_MenuAssembly.swift │ ├── MenuAssembly.swift │ ├── ViewControllers │ │ └── MenuViewController.swift │ └── ViewModels │ │ ├── MainMenuItemViewModel.swift │ │ ├── MainMenuViewModel.swift │ │ ├── MenuItemViewModel.swift │ │ └── MenuViewModel.swift │ └── TableExample │ ├── Container_TableExampleAssembly.swift │ ├── TableExampleAssembly.swift │ ├── TableExampleCell.swift │ └── TableExampleViewController.swift ├── ExampleTests ├── .swiftlint.yml ├── Application │ └── AppDelegateSpec.swift ├── Doubles │ ├── NavigationControllerSpy.swift │ └── WindowSpy.swift ├── Info.plist └── UI │ ├── MainMenuItemViewModelSpec.swift │ ├── MainMenuViewModelSpec.swift │ ├── MenuViewControllerSpec.swift │ ├── TableExampleCellSpec.swift │ └── TableExampleViewControllerSpec.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Misc └── SwipeToReveal-screenshot1.gif ├── Podfile ├── Podfile.lock ├── README.md ├── Source └── SwipeToRevealView.swift ├── SwipeToReveal.podspec ├── SwipeToReveal.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── SwipeToReveal Example Tests.xcscheme │ ├── SwipeToReveal Example.xcscheme │ └── SwipeToReveal Tests.xcscheme ├── SwipeToReveal.xcworkspace └── contents.xcworkspacedata └── Tests ├── Info.plist └── SwipeToRevealViewSpec.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ################################ 2 | # OS X 3 | 4 | *.DS_Store 5 | *.swp 6 | .Trashes 7 | 8 | ################################ 9 | # Xcode 10 | 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspective 18 | !default.perspective 19 | *.perspectivev3 20 | !default.perspectivev3 21 | *.xcuserstate 22 | project.xcworkspace/ 23 | xcuserdata/ 24 | build/ 25 | dist/ 26 | DerivedData/ 27 | *.moved-aside 28 | *.xccheckout 29 | 30 | ################################ 31 | # AppCode 32 | 33 | .idea 34 | 35 | ################################ 36 | # Backup files 37 | 38 | *~ 39 | *~.nib 40 | *~.xib 41 | \#*# 42 | .#* 43 | 44 | ################################ 45 | # CocoaPods 46 | 47 | Pods/ 48 | !Podfile.lock 49 | 50 | ################################ 51 | # Fastlane 52 | 53 | fastlane/report.xml 54 | fastlane/test_output 55 | fastlane/screenshots 56 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.2 3 | cache: false 4 | sudo: false 5 | rvm: 6 | - 2.3.3 7 | before_install: 8 | - gem install bundler 9 | - bundle install --without=documentation 10 | - bundle exec pod install --repo-update 11 | script: 12 | - set -o pipefail && xcodebuild test -workspace SwipeToReveal.xcworkspace -scheme 'SwipeToReveal Tests' -destination 'id=34FA4749-C467-4D45-9F8E-E31AEDDC39C2' ONLY_ACTIVE_ARCH=NO | xcpretty 13 | - set -o pipefail && xcodebuild test -workspace SwipeToReveal.xcworkspace -scheme 'SwipeToReveal Example Tests' -destination 'id=34FA4749-C467-4D45-9F8E-E31AEDDC39C2' ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | opt_in_rules: 2 | - empty_count 3 | - force_unwrapping 4 | - private_outlet 5 | - redundant_nil_coalesing 6 | 7 | excluded: 8 | - Pods 9 | 10 | line_length: 11 | warning: 120 12 | error: 200 13 | 14 | variable_name: 15 | excluded: 16 | - id 17 | - URL 18 | - to 19 | - rx 20 | -------------------------------------------------------------------------------- /Example/Application/Container.swift: -------------------------------------------------------------------------------- 1 | class Container {} 2 | -------------------------------------------------------------------------------- /Example/Application/Delegate/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder { 5 | 6 | override convenience init() { 7 | self.init(assembly: Container().appDelegateAssembly) 8 | } 9 | 10 | init(assembly: AppDelegateAssembly) { 11 | self.assembly = assembly 12 | super.init() 13 | } 14 | 15 | fileprivate let assembly: AppDelegateAssembly 16 | var window: UIWindow? 17 | 18 | } 19 | 20 | extension AppDelegate: UIApplicationDelegate { 21 | 22 | func application(_ application: UIApplication, 23 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 24 | window = assembly.window 25 | let navigationController = assembly.navigationController 26 | navigationController.viewControllers = [assembly.rootViewController] 27 | window?.rootViewController = navigationController 28 | window?.makeKeyAndVisible() 29 | return true 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/Application/Delegate/AppDelegateAssembly.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol AppDelegateAssembly { 4 | var window: UIWindow { get } 5 | var navigationController: UINavigationController { get } 6 | var rootViewController: UIViewController { get } 7 | } 8 | -------------------------------------------------------------------------------- /Example/Application/Delegate/Container_AppDelegateAssembly.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension Container { 4 | var appDelegateAssembly: AppDelegateAssembly { 5 | struct Assembly: AppDelegateAssembly { 6 | 7 | let container: Container 8 | 9 | var window: UIWindow { 10 | return UIWindow(frame: UIScreen.main.bounds) 11 | } 12 | 13 | var navigationController: UINavigationController { 14 | return UINavigationController() 15 | } 16 | 17 | var rootViewController: UIViewController { 18 | return MenuViewController(assembly: container.menuAssembly) 19 | } 20 | 21 | } 22 | return Assembly(container: self) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/Application/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeRight 33 | UIInterfaceOrientationLandscapeLeft 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Example/UI/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/UI/Menu/Container_MenuAssembly.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension Container { 4 | var menuAssembly: MenuAssembly { 5 | struct Assembly: MenuAssembly { 6 | 7 | let container: Container 8 | 9 | var viewModel: MenuViewModel { 10 | return MainMenuViewModel(assembly: self) 11 | } 12 | 13 | var tableExampleViewController: UIViewController { 14 | return TableExampleViewController(assembly: container.tableExampleAssembly) 15 | } 16 | 17 | } 18 | return Assembly(container: self) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/UI/Menu/MenuAssembly.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol MenuAssembly { 4 | var viewModel: MenuViewModel { get } 5 | var tableExampleViewController: UIViewController { get } 6 | } 7 | -------------------------------------------------------------------------------- /Example/UI/Menu/ViewControllers/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class MenuViewController: UITableViewController, MenuViewModelDelegate { 4 | 5 | init(assembly: MenuAssembly) { 6 | self.assembly = assembly 7 | viewModel = assembly.viewModel 8 | super.init(nibName: nil, bundle: nil) 9 | viewModel.delegate = self 10 | } 11 | 12 | required init?(coder aDecoder: NSCoder) { 13 | fatalError("init(coder:) has not been implemented") 14 | } 15 | 16 | // MARK: View 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | navigationItem.title = viewModel.title 21 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 22 | tableView.tableFooterView = UIView(frame: .zero) 23 | } 24 | 25 | // MARK: UITableViewDataSource 26 | 27 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 28 | guard section == 0 else { return 0 } 29 | return viewModel.items.count 30 | } 31 | 32 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 33 | guard indexPath.section == 0 else { fatalError() } 34 | let viewModel = self.viewModel.items[indexPath.row] 35 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 36 | cell.accessoryType = .disclosureIndicator 37 | cell.textLabel?.text = viewModel.title 38 | return cell 39 | } 40 | 41 | // MARK: UITableViewDelegate 42 | 43 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 44 | guard indexPath.section == 0 else { fatalError() } 45 | let viewModel = self.viewModel.items[indexPath.row] 46 | viewModel.select() 47 | } 48 | 49 | // MARK: MenuViewModelDelegate 50 | 51 | func menuViewModel(_ viewModel: MenuViewModel, presentViewController viewController: UIViewController) { 52 | guard viewModel === self.viewModel else { fatalError() } 53 | navigationController?.pushViewController(viewController, animated: true) 54 | } 55 | 56 | // MARK: Private 57 | 58 | private let assembly: MenuAssembly 59 | private let viewModel: MenuViewModel 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Example/UI/Menu/ViewModels/MainMenuItemViewModel.swift: -------------------------------------------------------------------------------- 1 | class MainMenuItemViewModel: MenuItemViewModel { 2 | 3 | init(title: String) { 4 | self.title = title 5 | } 6 | 7 | // MARK: MenuItemViewModel 8 | 9 | weak var delegate: MenuItemViewModelDelegate? 10 | 11 | let title: String 12 | 13 | func select() { 14 | delegate?.menuItemViewModelDidSelect(self) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Example/UI/Menu/ViewModels/MainMenuViewModel.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class MainMenuViewModel: MenuViewModel, MenuItemViewModelDelegate { 4 | 5 | init(assembly: MenuAssembly) { 6 | self.assembly = assembly 7 | tableViewExample.delegate = self 8 | } 9 | 10 | weak var delegate: MenuViewModelDelegate? 11 | private let assembly: MenuAssembly 12 | private let tableViewExample = MainMenuItemViewModel(title: "Table View Cell Example") 13 | 14 | // MARK: MenuViewModel 15 | 16 | let title = "SwipeToReveal" 17 | 18 | var items: [MenuItemViewModel] { 19 | return [tableViewExample] 20 | } 21 | 22 | // MARK: MenuItemViewModelDelegate 23 | 24 | func menuItemViewModelDidSelect(_ viewModel: MenuItemViewModel) { 25 | if viewModel === tableViewExample { 26 | let viewController = assembly.tableExampleViewController 27 | delegate?.menuViewModel(self, presentViewController: viewController) 28 | } else { 29 | fatalError() 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Example/UI/Menu/ViewModels/MenuItemViewModel.swift: -------------------------------------------------------------------------------- 1 | protocol MenuItemViewModel: class { 2 | weak var delegate: MenuItemViewModelDelegate? { get set } 3 | var title: String { get } 4 | func select() 5 | } 6 | 7 | protocol MenuItemViewModelDelegate: class { 8 | func menuItemViewModelDidSelect(_ viewModel: MenuItemViewModel) 9 | } 10 | -------------------------------------------------------------------------------- /Example/UI/Menu/ViewModels/MenuViewModel.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol MenuViewModel: class { 4 | weak var delegate: MenuViewModelDelegate? { get set } 5 | var title: String { get } 6 | var items: [MenuItemViewModel] { get } 7 | } 8 | 9 | protocol MenuViewModelDelegate: class { 10 | func menuViewModel(_ viewModel: MenuViewModel, presentViewController viewController: UIViewController) 11 | } 12 | -------------------------------------------------------------------------------- /Example/UI/TableExample/Container_TableExampleAssembly.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension Container { 4 | var tableExampleAssembly: TableExampleAssembly { 5 | struct Assembly: TableExampleAssembly {} 6 | return Assembly() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Example/UI/TableExample/TableExampleAssembly.swift: -------------------------------------------------------------------------------- 1 | protocol TableExampleAssembly {} 2 | -------------------------------------------------------------------------------- /Example/UI/TableExample/TableExampleCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | import SwipeToReveal 4 | import Reusable 5 | 6 | class TableExampleCell: UITableViewCell, Reusable { 7 | 8 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 9 | super.init(style: style, reuseIdentifier: reuseIdentifier) 10 | loadSubviews() 11 | setupLayout() 12 | } 13 | 14 | required init?(coder aDecoder: NSCoder) { 15 | fatalError("init(coder:) has not been implemented") 16 | } 17 | 18 | override func prepareForReuse() { 19 | super.prepareForReuse() 20 | label.text = nil 21 | swipeToReveal.close(animated: false) 22 | } 23 | 24 | // MARK: Subviews 25 | 26 | let label = Factory.label 27 | let swipeToReveal = SwipeToRevealView(frame: .zero) 28 | let swipeButtonA = Factory.button(title: "Button A", color: .red) 29 | let swipeButtonB = Factory.button(title: "Button B", color: .blue) 30 | 31 | private func loadSubviews() { 32 | contentView.addSubview(swipeToReveal) 33 | swipeToReveal.contentView = swipeContentView 34 | swipeContentView.addSubview(label) 35 | swipeToReveal.rightView = swipeRightView 36 | swipeRightView.addSubview(swipeButtonA) 37 | swipeRightView.addSubview(swipeButtonB) 38 | } 39 | 40 | private let swipeContentView = Factory.view(layoutMargins: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)) 41 | private let swipeRightView = Factory.view() 42 | 43 | // MARK: Layout 44 | 45 | private func setupLayout() { 46 | swipeToReveal.snp.makeConstraints { 47 | $0.edges.equalToSuperview() 48 | } 49 | label.snp.makeConstraints { 50 | guard let superview = label.superview else { fatalError() } 51 | $0.centerYWithinMargins.equalToSuperview() 52 | $0.top.greaterThanOrEqualTo(superview.snp.topMargin) 53 | $0.left.equalTo(superview.snp.leftMargin) 54 | $0.right.lessThanOrEqualTo(superview.snp.rightMargin) 55 | $0.bottom.lessThanOrEqualTo(superview.snp.bottomMargin) 56 | } 57 | swipeButtonA.snp.makeConstraints { 58 | $0.top.left.bottom.equalToSuperview() 59 | } 60 | swipeButtonB.snp.makeConstraints { 61 | $0.left.equalTo(swipeButtonA.snp.right) 62 | $0.top.right.bottom.equalToSuperview() 63 | } 64 | } 65 | 66 | } 67 | 68 | private extension TableExampleCell { 69 | struct Factory { 70 | 71 | static var label: UILabel { 72 | let label = UILabel(frame: .zero) 73 | label.font = UIFont.systemFont(ofSize: UIFont.systemFontSize) 74 | label.textColor = .darkText 75 | return label 76 | } 77 | 78 | static func button(title: String, color: UIColor) -> UIButton { 79 | let button = UIButton(frame: .zero) 80 | button.setTitle(title, for: .normal) 81 | button.setTitleColor(.white, for: .normal) 82 | button.backgroundColor = color 83 | button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) 84 | return button 85 | } 86 | 87 | static func view(layoutMargins: UIEdgeInsets = .zero) -> UIView { 88 | let view = UIView(frame: .zero) 89 | view.layoutMargins = layoutMargins 90 | return view 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Example/UI/TableExample/TableExampleViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Reusable 3 | 4 | class TableExampleViewController: UITableViewController { 5 | 6 | init(assembly: TableExampleAssembly) { 7 | self.assembly = assembly 8 | super.init(style: .plain) 9 | } 10 | 11 | required init?(coder aDecoder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | tableView.register(cellType: TableExampleCell.self) 18 | } 19 | 20 | // MARK: UITableViewDataSource 21 | 22 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 23 | switch section { 24 | case 0: return 100 25 | default: fatalError() 26 | } 27 | } 28 | 29 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 30 | let cell: TableExampleCell = tableView.dequeueReusableCell(for: indexPath) 31 | cell.label.text = "Cell \(indexPath.row + 1)" 32 | return cell 33 | } 34 | 35 | // MARK: UITableViewDelegate 36 | 37 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 38 | switch (indexPath.section, indexPath.row) { 39 | case (0, _): 40 | return 88 41 | default: 42 | fatalError() 43 | } 44 | } 45 | 46 | // MARK: Private 47 | 48 | private let assembly: TableExampleAssembly 49 | 50 | } 51 | -------------------------------------------------------------------------------- /ExampleTests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | opt_in_rules: 2 | - empty_count 3 | - redundant_nil_coalesing 4 | 5 | disabled_rules: 6 | - line_length 7 | - variable_name 8 | - function_body_length 9 | -------------------------------------------------------------------------------- /ExampleTests/Application/AppDelegateSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import SwipeToRevealExample 4 | 5 | class AppDelegateSpec: QuickSpec { 6 | 7 | override func spec() { 8 | describe("AppDelegate") { 9 | var sut: AppDelegate! 10 | var assembly: Assembly! 11 | 12 | beforeEach { 13 | assembly = Assembly() 14 | sut = AppDelegate(assembly: assembly) 15 | } 16 | 17 | context("app did finish launching") { 18 | var returnValue: Bool! 19 | 20 | beforeEach { 21 | returnValue = sut.application(UIApplication.shared, didFinishLaunchingWithOptions: nil) 22 | } 23 | 24 | it("should return true") { 25 | expect(returnValue).to(beTrue()) 26 | } 27 | 28 | it("should make window key and visible") { 29 | expect(assembly.windowSpy.makeKeyAndVisibleCalled).to(beTrue()) 30 | } 31 | 32 | describe("root navigation controller") { 33 | var controller: UINavigationController! 34 | 35 | beforeEach { 36 | controller = sut.window?.rootViewController as? UINavigationController 37 | } 38 | 39 | it("should be correct") { 40 | expect(controller).to(equal(assembly.navigationController)) 41 | } 42 | 43 | it("should have correct root") { 44 | expect(controller.viewControllers.first).to(equal(assembly.rootViewController)) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | struct Assembly: AppDelegateAssembly { 52 | let windowSpy = WindowSpy(frame: .zero) 53 | var window: UIWindow { return windowSpy } 54 | let navigationController = UINavigationController() 55 | let rootViewController = UIViewController(nibName: nil, bundle: nil) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /ExampleTests/Doubles/NavigationControllerSpy.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class NavigationControllerSpy: UINavigationController { 4 | 5 | override func pushViewController(_ viewController: UIViewController, animated: Bool) { 6 | super.pushViewController(viewController, animated: animated) 7 | _didPushViewController = (viewController, animated) 8 | } 9 | 10 | private(set) var _didPushViewController: (viewController: UIViewController, animated: Bool)? 11 | 12 | func _reset() { 13 | _didPushViewController = nil 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ExampleTests/Doubles/WindowSpy.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class WindowSpy: UIWindow { 4 | 5 | private(set) var makeKeyAndVisibleCalled = false 6 | 7 | override func makeKeyAndVisible() { 8 | makeKeyAndVisibleCalled = true 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ExampleTests/UI/MainMenuItemViewModelSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import SwipeToRevealExample 4 | 5 | class MainMenuItemViewModelSpec: QuickSpec { 6 | 7 | override func spec() { 8 | describe("MainMenuItemViewModel") { 9 | var sut: MainMenuItemViewModel! 10 | var title: String! 11 | var delegate: Delegate! 12 | 13 | beforeEach { 14 | title = "Menu Item Title" 15 | delegate = Delegate() 16 | sut = MainMenuItemViewModel(title: title) 17 | sut.delegate = delegate 18 | } 19 | 20 | it("should have correct title") { 21 | expect(sut.title).to(equal(title)) 22 | } 23 | 24 | context("did select") { 25 | beforeEach { 26 | sut.select() 27 | } 28 | 29 | it("should call delegate") { 30 | expect(delegate._menuItemViewModelDidSelect).to(be(sut)) 31 | } 32 | } 33 | } 34 | } 35 | 36 | class Delegate: MenuItemViewModelDelegate { 37 | 38 | func menuItemViewModelDidSelect(_ viewModel: MenuItemViewModel) { 39 | _menuItemViewModelDidSelect = viewModel 40 | } 41 | 42 | private(set) var _menuItemViewModelDidSelect: MenuItemViewModel? 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /ExampleTests/UI/MainMenuViewModelSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import SwipeToRevealExample 4 | 5 | class MainMenuViewModelSpec: QuickSpec { 6 | 7 | override func spec() { 8 | describe("MainMenuViewModel") { 9 | var sut: MainMenuViewModel! 10 | var assembly: Assembly! 11 | var delegate: Delegate! 12 | 13 | beforeEach { 14 | assembly = Assembly() 15 | delegate = Delegate() 16 | sut = MainMenuViewModel(assembly: assembly) 17 | sut.delegate = delegate 18 | } 19 | 20 | it("should have one item") { 21 | expect(sut.items.count).to(equal(1)) 22 | } 23 | 24 | context("first item did select") { 25 | beforeEach { 26 | sut.menuItemViewModelDidSelect(sut.items.first!) 27 | } 28 | 29 | it("should call delegate") { 30 | expect(delegate._menuViewModelDidPresentViewController?.viewModel).to(be(sut)) 31 | } 32 | 33 | it("should present correct view controller") { 34 | expect(delegate._menuViewModelDidPresentViewController?.viewController) 35 | .to(be(assembly.tableExampleViewController)) 36 | } 37 | } 38 | 39 | context("unknown item did select") { 40 | it("should throw assertion") { 41 | expect { () -> Void in 42 | sut.menuItemViewModelDidSelect(MainMenuItemViewModel(title: "Unknown")) 43 | }.to(throwAssertion()) 44 | } 45 | } 46 | } 47 | } 48 | 49 | struct Assembly: MenuAssembly { 50 | var viewModel: MenuViewModel { fatalError() } 51 | let tableExampleViewController = UIViewController(nibName: nil, bundle: nil) 52 | } 53 | 54 | class Delegate: MenuViewModelDelegate { 55 | 56 | func menuViewModel(_ viewModel: MenuViewModel, 57 | presentViewController viewController: UIViewController) { 58 | _menuViewModelDidPresentViewController = (viewModel, viewController) 59 | } 60 | 61 | private(set) var _menuViewModelDidPresentViewController: 62 | (viewModel: MenuViewModel, viewController: UIViewController)? 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /ExampleTests/UI/MenuViewControllerSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import SwipeToRevealExample 4 | 5 | class MenuViewControllerSpec: QuickSpec { 6 | 7 | override func spec() { 8 | context("init with coder") { 9 | it("should throw asserion") { 10 | expect { () -> Void in _ = MenuViewController(coder: NSCoder()) }.to(throwAssertion()) 11 | } 12 | } 13 | 14 | context("init") { 15 | var sut: MenuViewController! 16 | var assembly: Assembly! 17 | var viewModel: ViewModel! 18 | 19 | beforeEach { 20 | viewModel = ViewModel() 21 | assembly = Assembly(viewModel: viewModel) 22 | sut = MenuViewController(assembly: assembly) 23 | } 24 | 25 | context("load view") { 26 | beforeEach { 27 | _ = sut.view 28 | } 29 | 30 | it("should have correct title") { 31 | expect(sut.navigationItem.title).to(equal(viewModel.title)) 32 | } 33 | 34 | describe("table view") { 35 | it("should have 1 section") { 36 | expect(sut.numberOfSections(in: sut.tableView)).to(equal(1)) 37 | } 38 | 39 | it("should have 3 rows in first section") { 40 | expect(sut.tableView(sut.tableView, numberOfRowsInSection: 0)).to(equal(3)) 41 | } 42 | 43 | it("should have 0 rows in second section") { 44 | expect(sut.tableView(sut.tableView, numberOfRowsInSection: 1)).to(equal(0)) 45 | } 46 | 47 | func testCell(section: Int, row: Int) { 48 | describe("cell \(row) in section \(section)") { 49 | var cell: UITableViewCell! 50 | 51 | beforeEach { 52 | cell = sut.tableView(sut.tableView, cellForRowAt: IndexPath(row: row, section: section)) 53 | } 54 | 55 | it("should have correct title") { 56 | expect(cell.textLabel?.text).to(equal(viewModel.items[row].title)) 57 | } 58 | 59 | it("should have correct accessory type") { 60 | expect(cell.accessoryType).to(equal(UITableViewCellAccessoryType.disclosureIndicator)) 61 | } 62 | 63 | it("should not be selected") { 64 | expect(viewModel._items[row]._isSelected).to(beFalse()) 65 | } 66 | 67 | context("select") { 68 | beforeEach { 69 | sut.tableView(sut.tableView, didSelectRowAt: IndexPath(row: row, section: section)) 70 | } 71 | 72 | it("should select view model") { 73 | expect(viewModel._items[row]._isSelected).to(beTrue()) 74 | } 75 | } 76 | } 77 | } 78 | 79 | testCell(section: 0, row: 0) 80 | testCell(section: 0, row: 1) 81 | testCell(section: 0, row: 2) 82 | 83 | func testInvalidCell(section: Int, row: Int) { 84 | describe("invalid cell \(row) in section \(section)") { 85 | it("should throw assertion") { 86 | expect { () -> Void in 87 | _ = sut.tableView(sut.tableView, cellForRowAt: IndexPath(row: row, section: section)) 88 | }.to(throwAssertion()) 89 | } 90 | 91 | it("should throw assertion when selected") { 92 | expect { () -> Void in 93 | sut.tableView(sut.tableView, didSelectRowAt: IndexPath(row: row, section: section)) 94 | }.to(throwAssertion()) 95 | } 96 | } 97 | } 98 | 99 | testInvalidCell(section: 0, row: 3) 100 | testInvalidCell(section: 1, row: 0) 101 | 102 | } 103 | } 104 | 105 | context("embedded in UINavigationController") { 106 | var navigationController: NavigationControllerSpy! 107 | 108 | beforeEach { 109 | navigationController = NavigationControllerSpy(rootViewController: sut) 110 | _ = navigationController.view 111 | _ = sut.view 112 | navigationController._reset() 113 | } 114 | 115 | context("menu view model presents view controller") { 116 | var viewController: UIViewController! 117 | 118 | beforeEach { 119 | viewController = UIViewController(nibName: nil, bundle: nil) 120 | sut.menuViewModel(viewModel, presentViewController: viewController) 121 | } 122 | 123 | it("should push correct view controller animated") { 124 | let result = navigationController._didPushViewController 125 | expect(result?.viewController).to(be(viewController)) 126 | expect(result?.animated).to(beTrue()) 127 | } 128 | } 129 | 130 | context("unknown menu view model presents view controller") { 131 | it("should throw asserion") { 132 | expect { () -> Void in 133 | sut.menuViewModel(ViewModel(), presentViewController: UIViewController(nibName: nil, bundle: nil)) 134 | }.to(throwAssertion()) 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | struct Assembly: MenuAssembly { 142 | let viewModel: MenuViewModel 143 | var tableExampleViewController: UIViewController { fatalError() } 144 | } 145 | 146 | class ViewModel: MenuViewModel { 147 | weak var delegate: MenuViewModelDelegate? 148 | 149 | let title = "Menu Title" 150 | 151 | var items: [MenuItemViewModel] { 152 | return _items 153 | } 154 | 155 | let _items: [ItemViewModel] = [ 156 | ItemViewModel(title: "Menu Item 1"), 157 | ItemViewModel(title: "Menu Item 2"), 158 | ItemViewModel(title: "Menu Item 3") 159 | ] 160 | } 161 | 162 | class ItemViewModel: MenuItemViewModel { 163 | init(title: String) { 164 | self.title = title 165 | } 166 | 167 | weak var delegate: MenuItemViewModelDelegate? 168 | 169 | let title: String 170 | 171 | func select() { 172 | _isSelected = true 173 | } 174 | 175 | private(set) var _isSelected = false 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /ExampleTests/UI/TableExampleCellSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import SwipeToRevealExample 4 | 5 | class TableExampleCellSpec: QuickSpec { 6 | 7 | override func spec() { 8 | context("init with coder") { 9 | it("should throw asserion") { 10 | expect { () -> Void in _ = TableExampleCell(coder: NSCoder()) }.to(throwAssertion()) 11 | } 12 | } 13 | 14 | context("init") { 15 | var sut: TableExampleCell! 16 | 17 | beforeEach { 18 | sut = TableExampleCell(style: .default, reuseIdentifier: "cell") 19 | } 20 | 21 | context("prepare for reuse") { 22 | beforeEach { 23 | sut.label.text = "Test" 24 | sut.prepareForReuse() 25 | } 26 | 27 | it("should clear label text") { 28 | expect(sut.label.text).to(beNil()) 29 | } 30 | } 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ExampleTests/UI/TableExampleViewControllerSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import SwipeToRevealExample 4 | 5 | class TableExampleViewControllerSpec: QuickSpec { 6 | 7 | override func spec() { 8 | context("init with coder") { 9 | it("should throw asserion") { 10 | expect { () -> Void in _ = TableExampleViewController(coder: NSCoder()) }.to(throwAssertion()) 11 | } 12 | } 13 | 14 | context("init") { 15 | var sut: TableExampleViewController! 16 | 17 | beforeEach { 18 | sut = TableExampleViewController(assembly: Assembly()) 19 | } 20 | 21 | context("load view") { 22 | beforeEach { 23 | _ = sut.view 24 | sut.view.frame = CGRect(x: 0, y: 0, width: 320, height: 240) 25 | } 26 | 27 | describe("table view") { 28 | it("should have 1 section") { 29 | expect(sut.numberOfSections(in: sut.tableView)).to(equal(1)) 30 | } 31 | 32 | it("should have 100 rows in first section") { 33 | expect(sut.tableView(sut.tableView, numberOfRowsInSection: 0)).to(equal(100)) 34 | } 35 | 36 | it("should throw when asked for number of rows in second section") { 37 | expect { () -> Void in 38 | _ = sut.tableView(sut.tableView, numberOfRowsInSection: 1) 39 | }.to(throwAssertion()) 40 | } 41 | 42 | it("shuld not throw when asked for cell at valid index path") { 43 | expect { () -> Void in 44 | _ = sut.tableView(sut.tableView, cellForRowAt: IndexPath(row: 0, section: 0)) 45 | }.notTo(throwAssertion()) 46 | } 47 | 48 | it("should return correct height for a row") { 49 | expect(sut.tableView(sut.tableView, heightForRowAt: IndexPath(row: 0, section: 0))) 50 | .to(equal(88)) 51 | } 52 | 53 | it("should throw when asked for row height at invalid index path") { 54 | expect { () -> Void in 55 | _ = sut.tableView(sut.tableView, heightForRowAt: IndexPath(row: 0, section: 1)) 56 | }.to(throwAssertion()) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | struct Assembly: TableExampleAssembly {} 64 | 65 | } 66 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.1', '>= 1.1.1' 4 | gem 'synx', '~> 0.2', '>= 0.2.1' 5 | gem 'xcpretty', '~> 0.2.4' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.5) 5 | activesupport (4.2.9) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (1.0.2) 11 | clamp (0.6.5) 12 | cocoapods (1.2.1) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.1, < 2.0) 15 | cocoapods-core (= 1.2.1) 16 | cocoapods-deintegrate (>= 1.0.1, < 2.0) 17 | cocoapods-downloader (>= 1.1.3, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.2.0, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.5.7) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.1) 30 | xcodeproj (>= 1.4.4, < 2.0) 31 | cocoapods-core (1.2.1) 32 | activesupport (>= 4.0.2, < 5) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.1) 36 | cocoapods-downloader (1.1.3) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.0.0) 41 | cocoapods-trunk (1.2.0) 42 | nap (>= 0.8, < 2.0) 43 | netrc (= 0.7.8) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | colorize (0.8.1) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.0.3) 51 | i18n (0.8.4) 52 | minitest (5.10.2) 53 | molinillo (0.5.7) 54 | nanaimo (0.2.3) 55 | nap (1.1.0) 56 | netrc (0.7.8) 57 | rouge (2.0.7) 58 | ruby-macho (1.1.0) 59 | synx (0.2.1) 60 | clamp (~> 0.6) 61 | colorize (~> 0.7) 62 | xcodeproj (~> 1.0) 63 | thread_safe (0.3.6) 64 | tzinfo (1.2.3) 65 | thread_safe (~> 0.1) 66 | xcodeproj (1.5.0) 67 | CFPropertyList (~> 2.3.3) 68 | claide (>= 1.0.2, < 2.0) 69 | colored2 (~> 3.1) 70 | nanaimo (~> 0.2.3) 71 | xcpretty (0.2.8) 72 | rouge (~> 2.0.7) 73 | 74 | PLATFORMS 75 | ruby 76 | 77 | DEPENDENCIES 78 | cocoapods (~> 1.1, >= 1.1.1) 79 | synx (~> 0.2, >= 0.2.1) 80 | xcpretty (~> 0.2.4) 81 | 82 | BUNDLED WITH 83 | 1.15.1 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dariusz Rybicki Darrarski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Misc/SwipeToReveal-screenshot1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrarski/SwipeToReveal-iOS/4507efca0fb0742ff98ca0700738fd174739b10b/Misc/SwipeToReveal-screenshot1.gif -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'git://github.com/CocoaPods/Specs.git' 2 | platform :ios, '9.0' 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | target 'SwipeToRevealExample' do 7 | pod 'SwipeToReveal', :path => '.' 8 | pod 'Reveal-SDK', :configurations => ['Debug'] 9 | pod 'SnapKit', '~> 3.0' 10 | pod 'SwiftLint', '~> 0.16' 11 | pod 'Reusable', '~> 4.0' 12 | end 13 | 14 | target 'SwipeToRevealExampleTests' do 15 | pod 'SwipeToReveal', :path => '.' 16 | pod 'Quick', '~> 1.1' 17 | pod 'Nimble', '~> 7.0' 18 | end 19 | 20 | target 'SwipeToRevealTests' do 21 | pod 'SwipeToReveal', :path => '.' 22 | pod 'Quick', '~> 1.1' 23 | pod 'Nimble', '~> 7.0' 24 | end 25 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (7.0.1) 3 | - Quick (1.1.0) 4 | - Reusable (4.0.0): 5 | - Reusable/Storyboard (= 4.0.0) 6 | - Reusable/View (= 4.0.0) 7 | - Reusable/Storyboard (4.0.0) 8 | - Reusable/View (4.0.0) 9 | - Reveal-SDK (8) 10 | - SnapKit (3.2.0) 11 | - SwiftLint (0.20.1) 12 | - SwipeToReveal (1.0.4): 13 | - SnapKit (~> 3.0) 14 | 15 | DEPENDENCIES: 16 | - Nimble (~> 7.0) 17 | - Quick (~> 1.1) 18 | - Reusable (~> 4.0) 19 | - Reveal-SDK 20 | - SnapKit (~> 3.0) 21 | - SwiftLint (~> 0.16) 22 | - SwipeToReveal (from `.`) 23 | 24 | EXTERNAL SOURCES: 25 | SwipeToReveal: 26 | :path: "." 27 | 28 | SPEC CHECKSUMS: 29 | Nimble: 657d000e11df8aebe27cdaf9d244de7f30ed87f7 30 | Quick: dafc587e21eed9f4cab3249b9f9015b0b7a7f71d 31 | Reusable: 98e5fff1e0e2e00872199699b276dde08ee56c07 32 | Reveal-SDK: 43be4e662864e937960d0d04d005135e29c4e53b 33 | SnapKit: 1ca44df72cfa543218d177cb8aab029d10d86ea7 34 | SwiftLint: f60095dc173a3f3ec505bb34f5229c3dfd779a54 35 | SwipeToReveal: 363d2a93389dcfe558fb3839fa1a90960c18261f 36 | 37 | PODFILE CHECKSUM: 113efe820790c24ee8fde67dd0fe4f6961d9b39e 38 | 39 | COCOAPODS: 1.2.1 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwipeToReveal iOS 2 | 3 | ![Swift v3.0](https://img.shields.io/badge/swift-v3.0-orange.svg) 4 | [![CocoaPods](https://img.shields.io/cocoapods/v/SwipeToReveal.svg)](https://cocoapods.org/pods/SwipeToReveal) 5 | [![Build Status](https://travis-ci.org/darrarski/SwipeToReveal-iOS.svg?branch=master)](https://travis-ci.org/darrarski/SwipeToReveal-iOS) 6 | 7 | Customizable swipe-to-reveal menu for iOS apps. 8 | 9 | ![SwipeToReveal screenshot 1](Misc/SwipeToReveal-screenshot1.gif "SwipeToReveal screenshot 1") 10 | 11 | ## Installation 12 | 13 | You can integrate `SwipeToReveal` with your project using CocoaPods. Just add this line to your `Podfile`: 14 | 15 | ```ruby 16 | pod 'SwipeToReveal', '~> 1.0' 17 | ``` 18 | 19 | ## Usage 20 | 21 | Check out included example project. It shows how to use swipe menu on `UITableViewCell`, but component could be embedded **anywhere** in the view hierarchy. All public headers are documented. 22 | 23 | 24 | ## License 25 | 26 | MIT License - check out [LICENSE](LICENSE) file. 27 | -------------------------------------------------------------------------------- /Source/SwipeToRevealView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | 4 | public protocol SwipeToRevealViewDelegate: class { 5 | func swipeToRevealView(_ view: SwipeToRevealView, didCloseAnimated animated: Bool) 6 | func swipeToRevealView(_ view: SwipeToRevealView, didRevealRightAnimated animated: Bool) 7 | func swipeToRevealView(_ view: SwipeToRevealView, didPan pan: UIPanGestureRecognizer) 8 | } 9 | 10 | /// Swipe-to-reveal view 11 | public class SwipeToRevealView: UIView { 12 | 13 | public weak var delegate: SwipeToRevealViewDelegate? 14 | 15 | /// Create SwipeToRevealView 16 | /// 17 | /// - Parameter frame: frame 18 | override public init(frame: CGRect) { 19 | super.init(frame: frame) 20 | loadSubviews() 21 | setupLayout() 22 | setupGestureRecognizing() 23 | } 24 | 25 | required public init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | /// Close, so not extra content is revealed 30 | /// 31 | /// - Parameter animated: perform with animation 32 | public func close(animated: Bool) { 33 | contentOffset = closedOffset 34 | layoutIfNeeded(animated: animated) 35 | delegate?.swipeToRevealView(self, didCloseAnimated: animated) 36 | } 37 | 38 | /// Reveal right view 39 | /// 40 | /// - Parameter animated: perform with animation 41 | public func revealRight(animated: Bool) { 42 | contentOffset = rightRevealedOffset 43 | layoutIfNeeded(animated: animated) 44 | delegate?.swipeToRevealView(self, didRevealRightAnimated: animated) 45 | } 46 | 47 | // MARK: Subviews 48 | 49 | /// Main view, displayed fully when no extra content is revealed 50 | public var contentView: UIView? { 51 | didSet { 52 | if let oldValue = oldValue { 53 | oldValue.removeFromSuperview() 54 | } 55 | if let newValue = contentView { 56 | centerContainer.addSubview(newValue) 57 | newValue.snp.makeConstraints { $0.edges.equalToSuperview() } 58 | } 59 | } 60 | } 61 | 62 | /// Right view, hidden by default, can be revelead with swipe gesture 63 | public var rightView: UIView? { 64 | didSet { 65 | if let oldValue = oldValue { 66 | oldValue.removeFromSuperview() 67 | } 68 | if let newValue = rightView { 69 | rightContainer.addSubview(newValue) 70 | newValue.snp.makeConstraints { $0.edges.equalToSuperview() } 71 | } 72 | } 73 | } 74 | 75 | private func loadSubviews() { 76 | addSubview(centerContainer) 77 | addSubview(rightContainer) 78 | } 79 | 80 | private let centerContainer = UIView(frame: .zero) 81 | private let rightContainer = UIView(frame: .zero) 82 | 83 | // MARK: Layout 84 | 85 | /// Content offset - changes when swiping 86 | public var contentOffset = CGFloat(0) { 87 | didSet { centerContainer.snp.updateConstraints { $0.left.equalTo(contentOffset) } } 88 | } 89 | 90 | /// Value for content offset in default state 91 | public let closedOffset = CGFloat(0) 92 | 93 | /// Value for content offset when right view is fully revealed 94 | public var rightRevealedOffset: CGFloat { 95 | return -rightContainer.frame.width 96 | } 97 | 98 | private func setupLayout() { 99 | centerContainer.snp.makeConstraints { 100 | $0.top.equalToSuperview() 101 | $0.bottom.equalToSuperview() 102 | $0.size.equalToSuperview() 103 | $0.left.equalTo(contentOffset) 104 | } 105 | rightContainer.snp.makeConstraints { 106 | $0.top.equalTo(centerContainer.snp.top) 107 | $0.left.equalTo(centerContainer.snp.right) 108 | $0.bottom.equalTo(centerContainer.snp.bottom) 109 | } 110 | } 111 | 112 | private func layoutIfNeeded(animated: Bool) { 113 | guard animated else { 114 | super.layoutIfNeeded() 115 | return 116 | } 117 | 118 | UIView.animate(withDuration: 0.2, 119 | delay: 0, 120 | options: UIViewAnimationOptions.curveEaseInOut, 121 | animations: { [weak self] in self?.layoutIfNeeded() }) 122 | } 123 | 124 | // MARK: Gesture recognizing 125 | 126 | private func setupGestureRecognizing() { 127 | panGeastureRecognizer.delegate = self 128 | panGeastureRecognizer.addTarget(self, action: #selector(self.panGestureRecognizerUpdate(_:))) 129 | addGestureRecognizer(panGeastureRecognizer) 130 | } 131 | 132 | private let panGeastureRecognizer = UIPanGestureRecognizer() 133 | 134 | private struct Pan { 135 | let startPoint: CGFloat 136 | let startOffset: CGFloat 137 | 138 | var currentPoint: CGFloat { 139 | didSet { previousPoint = oldValue } 140 | } 141 | 142 | private var previousPoint: CGFloat 143 | 144 | var lastDelta: CGFloat { 145 | return currentPoint - previousPoint 146 | } 147 | 148 | var delta: CGFloat { 149 | return currentPoint - startPoint 150 | } 151 | 152 | init(point: CGFloat, offset: CGFloat) { 153 | self.startPoint = point 154 | self.startOffset = offset 155 | self.currentPoint = point 156 | self.previousPoint = point 157 | } 158 | } 159 | 160 | private var pan: Pan? 161 | 162 | func panGestureRecognizerUpdate(_ pgr: UIPanGestureRecognizer) { 163 | switch pgr.state { 164 | case .possible: break 165 | case .began: 166 | handlePanBegan(point: pgr.translation(in: self).x) 167 | case .changed: 168 | handlePanChanged(point: pgr.translation(in: self).x) 169 | case .ended, .cancelled, .failed: 170 | handlePanEnded() 171 | } 172 | 173 | delegate?.swipeToRevealView(self, didPan: pgr) 174 | } 175 | 176 | private func handlePanBegan(point: CGFloat) { 177 | pan = Pan(point: point, offset: contentOffset) 178 | } 179 | 180 | private func handlePanChanged(point: CGFloat) { 181 | pan?.currentPoint = point 182 | guard let pan = pan else { return } 183 | let targetOffset = pan.startOffset + pan.delta 184 | contentOffset = max(rightRevealedOffset, min(closedOffset, targetOffset)) 185 | } 186 | 187 | private func handlePanEnded() { 188 | guard let pan = pan else { return } 189 | 190 | if pan.lastDelta > 0 { 191 | contentOffset = closedOffset 192 | } else { 193 | contentOffset = rightRevealedOffset 194 | } 195 | 196 | layoutIfNeeded(animated: true) 197 | self.pan = nil 198 | } 199 | 200 | } 201 | 202 | extension SwipeToRevealView: UIGestureRecognizerDelegate { 203 | 204 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 205 | shouldRecognizeSimultaneouslyWith 206 | otherGestureRecognizer: UIGestureRecognizer) -> Bool { 207 | return false 208 | } 209 | 210 | override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 211 | guard let pgr = gestureRecognizer as? UIPanGestureRecognizer else { fatalError() } 212 | let velocity = pgr.velocity(in: self) 213 | return fabs(velocity.x) > fabs(velocity.y) 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /SwipeToReveal.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwipeToReveal' 3 | s.version = '1.0.4' 4 | s.summary = 'Customizable swipe-to-reveal view for iOS apps' 5 | s.homepage = 'https://github.com/darrarski/SwipeToReveal-iOS' 6 | s.author = { 'Dariusz Rybicki' => 'darrarski@gmail.com' } 7 | s.social_media_url = 'https://twitter.com/darrarski' 8 | s.license = { :type => 'MIT', 9 | :file => 'LICENSE' } 10 | s.source = { :git => 'https://github.com/darrarski/SwipeToReveal-iOS.git', 11 | :tag => "v#{s.version}" } 12 | s.platform = :ios 13 | s.ios.deployment_target = '9.0' 14 | s.source_files = 'Source' 15 | s.requires_arc = true 16 | s.frameworks = 'UIKit' 17 | 18 | s.dependency 'SnapKit', '~> 3.0' 19 | end 20 | -------------------------------------------------------------------------------- /SwipeToReveal.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 310FC2CA9FD77D1D1EBAC032 /* Pods_SwipeToRevealExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42DF1915B99875D21FA7B4DA /* Pods_SwipeToRevealExampleTests.framework */; }; 11 | 311714D81E5A6B640029A012 /* MainMenuItemViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311714D61E5A6AF60029A012 /* MainMenuItemViewModelSpec.swift */; }; 12 | 311714DB1E5A6D5C0029A012 /* MainMenuViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311714D91E5A6CBA0029A012 /* MainMenuViewModelSpec.swift */; }; 13 | 314A558E1E48E1CC00DD410B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314A558D1E48E1CC00DD410B /* AppDelegate.swift */; }; 14 | 314A55901E48E1CC00DD410B /* TableExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314A558F1E48E1CC00DD410B /* TableExampleViewController.swift */; }; 15 | 314A55951E48E1CC00DD410B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 314A55941E48E1CC00DD410B /* Assets.xcassets */; }; 16 | 314A55981E48E1CC00DD410B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 314A55961E48E1CC00DD410B /* LaunchScreen.storyboard */; }; 17 | 314A55A31E48E8BA00DD410B /* TableExampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314A55A21E48E8BA00DD410B /* TableExampleCell.swift */; }; 18 | 315BBE071E5BA976006148B7 /* NavigationControllerSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BBE061E5BA976006148B7 /* NavigationControllerSpy.swift */; }; 19 | 318DD17B1E5D07F600F1A091 /* SwipeToRevealViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318DD1751E5CDCA100F1A091 /* SwipeToRevealViewSpec.swift */; }; 20 | 318EB7EB1E5A43D70072426E /* MenuViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318EB7E91E5A43340072426E /* MenuViewControllerSpec.swift */; }; 21 | 318EB7ED1E5A44610072426E /* MenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318EB7EC1E5A44610072426E /* MenuViewModel.swift */; }; 22 | 318EB7EF1E5A44AA0072426E /* MenuItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318EB7EE1E5A44AA0072426E /* MenuItemViewModel.swift */; }; 23 | 318EB7F11E5A44F60072426E /* MainMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318EB7F01E5A44F60072426E /* MainMenuViewModel.swift */; }; 24 | 318EB7F31E5A453D0072426E /* MainMenuItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318EB7F21E5A453D0072426E /* MainMenuItemViewModel.swift */; }; 25 | 31A076851E57C2FB00D82BD7 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076841E57C2FB00D82BD7 /* Container.swift */; }; 26 | 31A076871E57C31500D82BD7 /* AppDelegateAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076861E57C31500D82BD7 /* AppDelegateAssembly.swift */; }; 27 | 31A076891E57C33B00D82BD7 /* Container_AppDelegateAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076881E57C33B00D82BD7 /* Container_AppDelegateAssembly.swift */; }; 28 | 31A076981E57C6E100D82BD7 /* AppDelegateSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076961E57C67F00D82BD7 /* AppDelegateSpec.swift */; }; 29 | 31A076A31E57CD8A00D82BD7 /* WindowSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076A11E57CD6900D82BD7 /* WindowSpy.swift */; }; 30 | 31A076A81E57CEA400D82BD7 /* TableExampleAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076A71E57CEA400D82BD7 /* TableExampleAssembly.swift */; }; 31 | 31A076AA1E57CEB200D82BD7 /* Container_TableExampleAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076A91E57CEB200D82BD7 /* Container_TableExampleAssembly.swift */; }; 32 | 31A076AD1E57D27700D82BD7 /* MenuAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076AC1E57D27700D82BD7 /* MenuAssembly.swift */; }; 33 | 31A076AF1E57D28000D82BD7 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076AE1E57D28000D82BD7 /* MenuViewController.swift */; }; 34 | 31A076B11E57D29000D82BD7 /* Container_MenuAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A076B01E57D29000D82BD7 /* Container_MenuAssembly.swift */; }; 35 | 31D017871E5BB15800F89A35 /* TableExampleViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D017851E5BB15500F89A35 /* TableExampleViewControllerSpec.swift */; }; 36 | 31D0178A1E5BB76300F89A35 /* TableExampleCellSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D017881E5BB75000F89A35 /* TableExampleCellSpec.swift */; }; 37 | 7EA899CDFED923D2FBF21F26 /* Pods_SwipeToRevealExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53EFD90C736845F0F5D16D61 /* Pods_SwipeToRevealExample.framework */; }; 38 | DCEB024567E3A2397088E635 /* Pods_SwipeToRevealTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AE8394623A061A1D76EEDCF /* Pods_SwipeToRevealTests.framework */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXContainerItemProxy section */ 42 | 31A0769F1E57C8D900D82BD7 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = 314A55821E48E1CC00DD410B /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = 314A55891E48E1CC00DD410B; 47 | remoteInfo = SwipeToRevealExample; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | 0598EC71861C2CB0F06509AC /* Pods-SwipeToRevealTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwipeToRevealTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwipeToRevealTests/Pods-SwipeToRevealTests.debug.xcconfig"; sourceTree = ""; }; 53 | 1EBACFF15A65C0ED772D25B3 /* Pods-SwipeToRevealExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwipeToRevealExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwipeToRevealExample/Pods-SwipeToRevealExample.release.xcconfig"; sourceTree = ""; }; 54 | 2AE8394623A061A1D76EEDCF /* Pods_SwipeToRevealTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwipeToRevealTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 311714D61E5A6AF60029A012 /* MainMenuItemViewModelSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenuItemViewModelSpec.swift; sourceTree = ""; }; 56 | 311714D91E5A6CBA0029A012 /* MainMenuViewModelSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenuViewModelSpec.swift; sourceTree = ""; }; 57 | 314A558A1E48E1CC00DD410B /* SwipeToRevealExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwipeToRevealExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 314A558D1E48E1CC00DD410B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 314A558F1E48E1CC00DD410B /* TableExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableExampleViewController.swift; sourceTree = ""; }; 60 | 314A55941E48E1CC00DD410B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 314A55971E48E1CC00DD410B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 314A55991E48E1CC00DD410B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 314A55A21E48E8BA00DD410B /* TableExampleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableExampleCell.swift; sourceTree = ""; }; 64 | 315BBE061E5BA976006148B7 /* NavigationControllerSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationControllerSpy.swift; sourceTree = ""; }; 65 | 318DD16D1E5CDA9100F1A091 /* SwipeToRevealTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwipeToRevealTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 318DD1711E5CDA9100F1A091 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 318DD1751E5CDCA100F1A091 /* SwipeToRevealViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeToRevealViewSpec.swift; sourceTree = ""; }; 68 | 318EB7E91E5A43340072426E /* MenuViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewControllerSpec.swift; sourceTree = ""; }; 69 | 318EB7EC1E5A44610072426E /* MenuViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewModel.swift; sourceTree = ""; }; 70 | 318EB7EE1E5A44AA0072426E /* MenuItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuItemViewModel.swift; sourceTree = ""; }; 71 | 318EB7F01E5A44F60072426E /* MainMenuViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenuViewModel.swift; sourceTree = ""; }; 72 | 318EB7F21E5A453D0072426E /* MainMenuItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenuItemViewModel.swift; sourceTree = ""; }; 73 | 31A076841E57C2FB00D82BD7 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; 74 | 31A076861E57C31500D82BD7 /* AppDelegateAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateAssembly.swift; sourceTree = ""; }; 75 | 31A076881E57C33B00D82BD7 /* Container_AppDelegateAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container_AppDelegateAssembly.swift; sourceTree = ""; }; 76 | 31A0768E1E57C48A00D82BD7 /* SwipeToRevealExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwipeToRevealExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | 31A076921E57C48A00D82BD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | 31A076961E57C67F00D82BD7 /* AppDelegateSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateSpec.swift; sourceTree = ""; }; 79 | 31A076A11E57CD6900D82BD7 /* WindowSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowSpy.swift; sourceTree = ""; }; 80 | 31A076A71E57CEA400D82BD7 /* TableExampleAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableExampleAssembly.swift; sourceTree = ""; }; 81 | 31A076A91E57CEB200D82BD7 /* Container_TableExampleAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container_TableExampleAssembly.swift; sourceTree = ""; }; 82 | 31A076AC1E57D27700D82BD7 /* MenuAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuAssembly.swift; sourceTree = ""; }; 83 | 31A076AE1E57D28000D82BD7 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 84 | 31A076B01E57D29000D82BD7 /* Container_MenuAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container_MenuAssembly.swift; sourceTree = ""; }; 85 | 31D017851E5BB15500F89A35 /* TableExampleViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableExampleViewControllerSpec.swift; sourceTree = ""; }; 86 | 31D017881E5BB75000F89A35 /* TableExampleCellSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableExampleCellSpec.swift; sourceTree = ""; }; 87 | 42DF1915B99875D21FA7B4DA /* Pods_SwipeToRevealExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwipeToRevealExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 53EFD90C736845F0F5D16D61 /* Pods_SwipeToRevealExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwipeToRevealExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | 5B4979947EEC3481468A09BD /* Pods-SwipeToRevealExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwipeToRevealExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwipeToRevealExampleTests/Pods-SwipeToRevealExampleTests.debug.xcconfig"; sourceTree = ""; }; 90 | 965BBCD85442586E700FFB28 /* Pods-SwipeToRevealExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwipeToRevealExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwipeToRevealExample/Pods-SwipeToRevealExample.debug.xcconfig"; sourceTree = ""; }; 91 | E08F4E7EEFE7CC457E74E83D /* Pods-SwipeToRevealTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwipeToRevealTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwipeToRevealTests/Pods-SwipeToRevealTests.release.xcconfig"; sourceTree = ""; }; 92 | F736ACAA48911E20D6BD4B89 /* Pods-SwipeToRevealExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwipeToRevealExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwipeToRevealExampleTests/Pods-SwipeToRevealExampleTests.release.xcconfig"; sourceTree = ""; }; 93 | /* End PBXFileReference section */ 94 | 95 | /* Begin PBXFrameworksBuildPhase section */ 96 | 314A55871E48E1CC00DD410B /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | 7EA899CDFED923D2FBF21F26 /* Pods_SwipeToRevealExample.framework in Frameworks */, 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | 318DD16A1E5CDA9100F1A091 /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | DCEB024567E3A2397088E635 /* Pods_SwipeToRevealTests.framework in Frameworks */, 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | 31A0768B1E57C48A00D82BD7 /* Frameworks */ = { 113 | isa = PBXFrameworksBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | 310FC2CA9FD77D1D1EBAC032 /* Pods_SwipeToRevealExampleTests.framework in Frameworks */, 117 | ); 118 | runOnlyForDeploymentPostprocessing = 0; 119 | }; 120 | /* End PBXFrameworksBuildPhase section */ 121 | 122 | /* Begin PBXGroup section */ 123 | 311714DC1E5A6EE10029A012 /* ViewModels */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 318EB7F21E5A453D0072426E /* MainMenuItemViewModel.swift */, 127 | 318EB7F01E5A44F60072426E /* MainMenuViewModel.swift */, 128 | 318EB7EE1E5A44AA0072426E /* MenuItemViewModel.swift */, 129 | 318EB7EC1E5A44610072426E /* MenuViewModel.swift */, 130 | ); 131 | path = ViewModels; 132 | sourceTree = ""; 133 | }; 134 | 311714DD1E5A6EE70029A012 /* ViewControllers */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 31A076AE1E57D28000D82BD7 /* MenuViewController.swift */, 138 | ); 139 | path = ViewControllers; 140 | sourceTree = ""; 141 | }; 142 | 314A55811E48E1CC00DD410B = { 143 | isa = PBXGroup; 144 | children = ( 145 | 314A558C1E48E1CC00DD410B /* Example */, 146 | 31A0768F1E57C48A00D82BD7 /* ExampleTests */, 147 | C69363D1A96F41166FC06D26 /* Frameworks */, 148 | BCB2B849A7B008448623F7A5 /* Pods */, 149 | 314A558B1E48E1CC00DD410B /* Products */, 150 | 318DD16E1E5CDA9100F1A091 /* Tests */, 151 | ); 152 | sourceTree = ""; 153 | }; 154 | 314A558B1E48E1CC00DD410B /* Products */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 314A558A1E48E1CC00DD410B /* SwipeToRevealExample.app */, 158 | 31A0768E1E57C48A00D82BD7 /* SwipeToRevealExampleTests.xctest */, 159 | 318DD16D1E5CDA9100F1A091 /* SwipeToRevealTests.xctest */, 160 | ); 161 | name = Products; 162 | sourceTree = ""; 163 | }; 164 | 314A558C1E48E1CC00DD410B /* Example */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 314A559F1E48E36500DD410B /* Application */, 168 | 314A55A01E48E36F00DD410B /* Resources */, 169 | 314A55A11E48E37A00DD410B /* UI */, 170 | ); 171 | path = Example; 172 | sourceTree = ""; 173 | }; 174 | 314A559F1E48E36500DD410B /* Application */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 31A076B21E57D6D000D82BD7 /* Delegate */, 178 | 31A076841E57C2FB00D82BD7 /* Container.swift */, 179 | 314A55991E48E1CC00DD410B /* Info.plist */, 180 | ); 181 | path = Application; 182 | sourceTree = ""; 183 | }; 184 | 314A55A01E48E36F00DD410B /* Resources */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 314A55941E48E1CC00DD410B /* Assets.xcassets */, 188 | ); 189 | path = Resources; 190 | sourceTree = ""; 191 | }; 192 | 314A55A11E48E37A00DD410B /* UI */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 31A076AB1E57D26E00D82BD7 /* Menu */, 196 | 31A076831E57C0F600D82BD7 /* TableExample */, 197 | 314A55961E48E1CC00DD410B /* LaunchScreen.storyboard */, 198 | ); 199 | path = UI; 200 | sourceTree = ""; 201 | }; 202 | 315BBE051E5BA8ED006148B7 /* Doubles */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | 315BBE061E5BA976006148B7 /* NavigationControllerSpy.swift */, 206 | 31A076A11E57CD6900D82BD7 /* WindowSpy.swift */, 207 | ); 208 | path = Doubles; 209 | sourceTree = ""; 210 | }; 211 | 318DD16E1E5CDA9100F1A091 /* Tests */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | 318DD1711E5CDA9100F1A091 /* Info.plist */, 215 | 318DD1751E5CDCA100F1A091 /* SwipeToRevealViewSpec.swift */, 216 | ); 217 | path = Tests; 218 | sourceTree = ""; 219 | }; 220 | 318EB7E81E5A43260072426E /* UI */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 311714D61E5A6AF60029A012 /* MainMenuItemViewModelSpec.swift */, 224 | 311714D91E5A6CBA0029A012 /* MainMenuViewModelSpec.swift */, 225 | 318EB7E91E5A43340072426E /* MenuViewControllerSpec.swift */, 226 | 31D017881E5BB75000F89A35 /* TableExampleCellSpec.swift */, 227 | 31D017851E5BB15500F89A35 /* TableExampleViewControllerSpec.swift */, 228 | ); 229 | path = UI; 230 | sourceTree = ""; 231 | }; 232 | 31A076831E57C0F600D82BD7 /* TableExample */ = { 233 | isa = PBXGroup; 234 | children = ( 235 | 31A076A91E57CEB200D82BD7 /* Container_TableExampleAssembly.swift */, 236 | 31A076A71E57CEA400D82BD7 /* TableExampleAssembly.swift */, 237 | 314A55A21E48E8BA00DD410B /* TableExampleCell.swift */, 238 | 314A558F1E48E1CC00DD410B /* TableExampleViewController.swift */, 239 | ); 240 | path = TableExample; 241 | sourceTree = ""; 242 | }; 243 | 31A0768F1E57C48A00D82BD7 /* ExampleTests */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | 31A076A51E57CDB700D82BD7 /* Application */, 247 | 315BBE051E5BA8ED006148B7 /* Doubles */, 248 | 318EB7E81E5A43260072426E /* UI */, 249 | 31A076921E57C48A00D82BD7 /* Info.plist */, 250 | ); 251 | path = ExampleTests; 252 | sourceTree = ""; 253 | }; 254 | 31A076A51E57CDB700D82BD7 /* Application */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | 31A076961E57C67F00D82BD7 /* AppDelegateSpec.swift */, 258 | ); 259 | path = Application; 260 | sourceTree = ""; 261 | }; 262 | 31A076AB1E57D26E00D82BD7 /* Menu */ = { 263 | isa = PBXGroup; 264 | children = ( 265 | 311714DD1E5A6EE70029A012 /* ViewControllers */, 266 | 311714DC1E5A6EE10029A012 /* ViewModels */, 267 | 31A076B01E57D29000D82BD7 /* Container_MenuAssembly.swift */, 268 | 31A076AC1E57D27700D82BD7 /* MenuAssembly.swift */, 269 | ); 270 | path = Menu; 271 | sourceTree = ""; 272 | }; 273 | 31A076B21E57D6D000D82BD7 /* Delegate */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | 314A558D1E48E1CC00DD410B /* AppDelegate.swift */, 277 | 31A076861E57C31500D82BD7 /* AppDelegateAssembly.swift */, 278 | 31A076881E57C33B00D82BD7 /* Container_AppDelegateAssembly.swift */, 279 | ); 280 | path = Delegate; 281 | sourceTree = ""; 282 | }; 283 | BCB2B849A7B008448623F7A5 /* Pods */ = { 284 | isa = PBXGroup; 285 | children = ( 286 | 965BBCD85442586E700FFB28 /* Pods-SwipeToRevealExample.debug.xcconfig */, 287 | 1EBACFF15A65C0ED772D25B3 /* Pods-SwipeToRevealExample.release.xcconfig */, 288 | 5B4979947EEC3481468A09BD /* Pods-SwipeToRevealExampleTests.debug.xcconfig */, 289 | F736ACAA48911E20D6BD4B89 /* Pods-SwipeToRevealExampleTests.release.xcconfig */, 290 | 0598EC71861C2CB0F06509AC /* Pods-SwipeToRevealTests.debug.xcconfig */, 291 | E08F4E7EEFE7CC457E74E83D /* Pods-SwipeToRevealTests.release.xcconfig */, 292 | ); 293 | name = Pods; 294 | sourceTree = ""; 295 | }; 296 | C69363D1A96F41166FC06D26 /* Frameworks */ = { 297 | isa = PBXGroup; 298 | children = ( 299 | 53EFD90C736845F0F5D16D61 /* Pods_SwipeToRevealExample.framework */, 300 | 42DF1915B99875D21FA7B4DA /* Pods_SwipeToRevealExampleTests.framework */, 301 | 2AE8394623A061A1D76EEDCF /* Pods_SwipeToRevealTests.framework */, 302 | ); 303 | name = Frameworks; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXGroup section */ 307 | 308 | /* Begin PBXNativeTarget section */ 309 | 314A55891E48E1CC00DD410B /* SwipeToRevealExample */ = { 310 | isa = PBXNativeTarget; 311 | buildConfigurationList = 314A559C1E48E1CC00DD410B /* Build configuration list for PBXNativeTarget "SwipeToRevealExample" */; 312 | buildPhases = ( 313 | AA4CD1C2AB847AF1BB96EF69 /* [CP] Check Pods Manifest.lock */, 314 | 318C5D7F1E49258900F41A97 /* SwiftLint */, 315 | 314A55861E48E1CC00DD410B /* Sources */, 316 | 314A55871E48E1CC00DD410B /* Frameworks */, 317 | 314A55881E48E1CC00DD410B /* Resources */, 318 | C262E636F62C6644F88B968D /* [CP] Embed Pods Frameworks */, 319 | 0F88EC1106ADB641B578B551 /* [CP] Copy Pods Resources */, 320 | ); 321 | buildRules = ( 322 | ); 323 | dependencies = ( 324 | ); 325 | name = SwipeToRevealExample; 326 | productName = SwipeToReveal; 327 | productReference = 314A558A1E48E1CC00DD410B /* SwipeToRevealExample.app */; 328 | productType = "com.apple.product-type.application"; 329 | }; 330 | 318DD16C1E5CDA9100F1A091 /* SwipeToRevealTests */ = { 331 | isa = PBXNativeTarget; 332 | buildConfigurationList = 318DD1741E5CDA9100F1A091 /* Build configuration list for PBXNativeTarget "SwipeToRevealTests" */; 333 | buildPhases = ( 334 | 9EB6C548AD7FC88BBCF43864 /* [CP] Check Pods Manifest.lock */, 335 | 318DD1691E5CDA9100F1A091 /* Sources */, 336 | 318DD16A1E5CDA9100F1A091 /* Frameworks */, 337 | 318DD16B1E5CDA9100F1A091 /* Resources */, 338 | B0F66F529111FE74320D711D /* [CP] Embed Pods Frameworks */, 339 | 2E2916B0D5B5F1D23A0CA30E /* [CP] Copy Pods Resources */, 340 | ); 341 | buildRules = ( 342 | ); 343 | dependencies = ( 344 | ); 345 | name = SwipeToRevealTests; 346 | productName = SwipeToRevealTests; 347 | productReference = 318DD16D1E5CDA9100F1A091 /* SwipeToRevealTests.xctest */; 348 | productType = "com.apple.product-type.bundle.unit-test"; 349 | }; 350 | 31A0768D1E57C48A00D82BD7 /* SwipeToRevealExampleTests */ = { 351 | isa = PBXNativeTarget; 352 | buildConfigurationList = 31A076931E57C48A00D82BD7 /* Build configuration list for PBXNativeTarget "SwipeToRevealExampleTests" */; 353 | buildPhases = ( 354 | 99A4A0FB20A5163EC2AC6A82 /* [CP] Check Pods Manifest.lock */, 355 | 318EB7F41E5A54DE0072426E /* SwiftLint */, 356 | 31A0768A1E57C48A00D82BD7 /* Sources */, 357 | 31A0768B1E57C48A00D82BD7 /* Frameworks */, 358 | 31A0768C1E57C48A00D82BD7 /* Resources */, 359 | 2C205B5580F89473F535612E /* [CP] Embed Pods Frameworks */, 360 | EF924DFD64A744014DBA68DD /* [CP] Copy Pods Resources */, 361 | ); 362 | buildRules = ( 363 | ); 364 | dependencies = ( 365 | 31A076A01E57C8D900D82BD7 /* PBXTargetDependency */, 366 | ); 367 | name = SwipeToRevealExampleTests; 368 | productName = SwipeToRevealExampleTests; 369 | productReference = 31A0768E1E57C48A00D82BD7 /* SwipeToRevealExampleTests.xctest */; 370 | productType = "com.apple.product-type.bundle.unit-test"; 371 | }; 372 | /* End PBXNativeTarget section */ 373 | 374 | /* Begin PBXProject section */ 375 | 314A55821E48E1CC00DD410B /* Project object */ = { 376 | isa = PBXProject; 377 | attributes = { 378 | LastSwiftUpdateCheck = 0820; 379 | LastUpgradeCheck = 0820; 380 | ORGANIZATIONNAME = Darrarski; 381 | TargetAttributes = { 382 | 314A55891E48E1CC00DD410B = { 383 | CreatedOnToolsVersion = 8.2.1; 384 | ProvisioningStyle = Manual; 385 | }; 386 | 318DD16C1E5CDA9100F1A091 = { 387 | CreatedOnToolsVersion = 8.2.1; 388 | ProvisioningStyle = Manual; 389 | }; 390 | 31A0768D1E57C48A00D82BD7 = { 391 | CreatedOnToolsVersion = 8.2.1; 392 | ProvisioningStyle = Manual; 393 | TestTargetID = 314A55891E48E1CC00DD410B; 394 | }; 395 | }; 396 | }; 397 | buildConfigurationList = 314A55851E48E1CC00DD410B /* Build configuration list for PBXProject "SwipeToReveal" */; 398 | compatibilityVersion = "Xcode 3.2"; 399 | developmentRegion = English; 400 | hasScannedForEncodings = 0; 401 | knownRegions = ( 402 | en, 403 | Base, 404 | ); 405 | mainGroup = 314A55811E48E1CC00DD410B; 406 | productRefGroup = 314A558B1E48E1CC00DD410B /* Products */; 407 | projectDirPath = ""; 408 | projectRoot = ""; 409 | targets = ( 410 | 314A55891E48E1CC00DD410B /* SwipeToRevealExample */, 411 | 31A0768D1E57C48A00D82BD7 /* SwipeToRevealExampleTests */, 412 | 318DD16C1E5CDA9100F1A091 /* SwipeToRevealTests */, 413 | ); 414 | }; 415 | /* End PBXProject section */ 416 | 417 | /* Begin PBXResourcesBuildPhase section */ 418 | 314A55881E48E1CC00DD410B /* Resources */ = { 419 | isa = PBXResourcesBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | 314A55981E48E1CC00DD410B /* LaunchScreen.storyboard in Resources */, 423 | 314A55951E48E1CC00DD410B /* Assets.xcassets in Resources */, 424 | ); 425 | runOnlyForDeploymentPostprocessing = 0; 426 | }; 427 | 318DD16B1E5CDA9100F1A091 /* Resources */ = { 428 | isa = PBXResourcesBuildPhase; 429 | buildActionMask = 2147483647; 430 | files = ( 431 | ); 432 | runOnlyForDeploymentPostprocessing = 0; 433 | }; 434 | 31A0768C1E57C48A00D82BD7 /* Resources */ = { 435 | isa = PBXResourcesBuildPhase; 436 | buildActionMask = 2147483647; 437 | files = ( 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | /* End PBXResourcesBuildPhase section */ 442 | 443 | /* Begin PBXShellScriptBuildPhase section */ 444 | 0F88EC1106ADB641B578B551 /* [CP] Copy Pods Resources */ = { 445 | isa = PBXShellScriptBuildPhase; 446 | buildActionMask = 2147483647; 447 | files = ( 448 | ); 449 | inputPaths = ( 450 | ); 451 | name = "[CP] Copy Pods Resources"; 452 | outputPaths = ( 453 | ); 454 | runOnlyForDeploymentPostprocessing = 0; 455 | shellPath = /bin/sh; 456 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwipeToRevealExample/Pods-SwipeToRevealExample-resources.sh\"\n"; 457 | showEnvVarsInLog = 0; 458 | }; 459 | 2C205B5580F89473F535612E /* [CP] Embed Pods Frameworks */ = { 460 | isa = PBXShellScriptBuildPhase; 461 | buildActionMask = 2147483647; 462 | files = ( 463 | ); 464 | inputPaths = ( 465 | ); 466 | name = "[CP] Embed Pods Frameworks"; 467 | outputPaths = ( 468 | ); 469 | runOnlyForDeploymentPostprocessing = 0; 470 | shellPath = /bin/sh; 471 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwipeToRevealExampleTests/Pods-SwipeToRevealExampleTests-frameworks.sh\"\n"; 472 | showEnvVarsInLog = 0; 473 | }; 474 | 2E2916B0D5B5F1D23A0CA30E /* [CP] Copy Pods Resources */ = { 475 | isa = PBXShellScriptBuildPhase; 476 | buildActionMask = 2147483647; 477 | files = ( 478 | ); 479 | inputPaths = ( 480 | ); 481 | name = "[CP] Copy Pods Resources"; 482 | outputPaths = ( 483 | ); 484 | runOnlyForDeploymentPostprocessing = 0; 485 | shellPath = /bin/sh; 486 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwipeToRevealTests/Pods-SwipeToRevealTests-resources.sh\"\n"; 487 | showEnvVarsInLog = 0; 488 | }; 489 | 318C5D7F1E49258900F41A97 /* SwiftLint */ = { 490 | isa = PBXShellScriptBuildPhase; 491 | buildActionMask = 2147483647; 492 | files = ( 493 | ); 494 | inputPaths = ( 495 | ); 496 | name = SwiftLint; 497 | outputPaths = ( 498 | ); 499 | runOnlyForDeploymentPostprocessing = 0; 500 | shellPath = /bin/sh; 501 | shellScript = "cd Example/; ${PODS_ROOT}/SwiftLint/swiftlint"; 502 | }; 503 | 318EB7F41E5A54DE0072426E /* SwiftLint */ = { 504 | isa = PBXShellScriptBuildPhase; 505 | buildActionMask = 2147483647; 506 | files = ( 507 | ); 508 | inputPaths = ( 509 | ); 510 | name = SwiftLint; 511 | outputPaths = ( 512 | ); 513 | runOnlyForDeploymentPostprocessing = 0; 514 | shellPath = /bin/sh; 515 | shellScript = "cd ExampleTests/; ${PODS_ROOT}/SwiftLint/swiftlint"; 516 | }; 517 | 99A4A0FB20A5163EC2AC6A82 /* [CP] Check Pods Manifest.lock */ = { 518 | isa = PBXShellScriptBuildPhase; 519 | buildActionMask = 2147483647; 520 | files = ( 521 | ); 522 | inputPaths = ( 523 | ); 524 | name = "[CP] Check Pods Manifest.lock"; 525 | outputPaths = ( 526 | ); 527 | runOnlyForDeploymentPostprocessing = 0; 528 | shellPath = /bin/sh; 529 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 530 | showEnvVarsInLog = 0; 531 | }; 532 | 9EB6C548AD7FC88BBCF43864 /* [CP] Check Pods Manifest.lock */ = { 533 | isa = PBXShellScriptBuildPhase; 534 | buildActionMask = 2147483647; 535 | files = ( 536 | ); 537 | inputPaths = ( 538 | ); 539 | name = "[CP] Check Pods Manifest.lock"; 540 | outputPaths = ( 541 | ); 542 | runOnlyForDeploymentPostprocessing = 0; 543 | shellPath = /bin/sh; 544 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 545 | showEnvVarsInLog = 0; 546 | }; 547 | AA4CD1C2AB847AF1BB96EF69 /* [CP] Check Pods Manifest.lock */ = { 548 | isa = PBXShellScriptBuildPhase; 549 | buildActionMask = 2147483647; 550 | files = ( 551 | ); 552 | inputPaths = ( 553 | ); 554 | name = "[CP] Check Pods Manifest.lock"; 555 | outputPaths = ( 556 | ); 557 | runOnlyForDeploymentPostprocessing = 0; 558 | shellPath = /bin/sh; 559 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 560 | showEnvVarsInLog = 0; 561 | }; 562 | B0F66F529111FE74320D711D /* [CP] Embed Pods Frameworks */ = { 563 | isa = PBXShellScriptBuildPhase; 564 | buildActionMask = 2147483647; 565 | files = ( 566 | ); 567 | inputPaths = ( 568 | ); 569 | name = "[CP] Embed Pods Frameworks"; 570 | outputPaths = ( 571 | ); 572 | runOnlyForDeploymentPostprocessing = 0; 573 | shellPath = /bin/sh; 574 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwipeToRevealTests/Pods-SwipeToRevealTests-frameworks.sh\"\n"; 575 | showEnvVarsInLog = 0; 576 | }; 577 | C262E636F62C6644F88B968D /* [CP] Embed Pods Frameworks */ = { 578 | isa = PBXShellScriptBuildPhase; 579 | buildActionMask = 2147483647; 580 | files = ( 581 | ); 582 | inputPaths = ( 583 | ); 584 | name = "[CP] Embed Pods Frameworks"; 585 | outputPaths = ( 586 | ); 587 | runOnlyForDeploymentPostprocessing = 0; 588 | shellPath = /bin/sh; 589 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwipeToRevealExample/Pods-SwipeToRevealExample-frameworks.sh\"\n"; 590 | showEnvVarsInLog = 0; 591 | }; 592 | EF924DFD64A744014DBA68DD /* [CP] Copy Pods Resources */ = { 593 | isa = PBXShellScriptBuildPhase; 594 | buildActionMask = 2147483647; 595 | files = ( 596 | ); 597 | inputPaths = ( 598 | ); 599 | name = "[CP] Copy Pods Resources"; 600 | outputPaths = ( 601 | ); 602 | runOnlyForDeploymentPostprocessing = 0; 603 | shellPath = /bin/sh; 604 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwipeToRevealExampleTests/Pods-SwipeToRevealExampleTests-resources.sh\"\n"; 605 | showEnvVarsInLog = 0; 606 | }; 607 | /* End PBXShellScriptBuildPhase section */ 608 | 609 | /* Begin PBXSourcesBuildPhase section */ 610 | 314A55861E48E1CC00DD410B /* Sources */ = { 611 | isa = PBXSourcesBuildPhase; 612 | buildActionMask = 2147483647; 613 | files = ( 614 | 314A55901E48E1CC00DD410B /* TableExampleViewController.swift in Sources */, 615 | 31A076891E57C33B00D82BD7 /* Container_AppDelegateAssembly.swift in Sources */, 616 | 318EB7F11E5A44F60072426E /* MainMenuViewModel.swift in Sources */, 617 | 314A558E1E48E1CC00DD410B /* AppDelegate.swift in Sources */, 618 | 31A076871E57C31500D82BD7 /* AppDelegateAssembly.swift in Sources */, 619 | 31A076AF1E57D28000D82BD7 /* MenuViewController.swift in Sources */, 620 | 318EB7F31E5A453D0072426E /* MainMenuItemViewModel.swift in Sources */, 621 | 31A076AA1E57CEB200D82BD7 /* Container_TableExampleAssembly.swift in Sources */, 622 | 31A076B11E57D29000D82BD7 /* Container_MenuAssembly.swift in Sources */, 623 | 31A076851E57C2FB00D82BD7 /* Container.swift in Sources */, 624 | 318EB7EF1E5A44AA0072426E /* MenuItemViewModel.swift in Sources */, 625 | 31A076AD1E57D27700D82BD7 /* MenuAssembly.swift in Sources */, 626 | 31A076A81E57CEA400D82BD7 /* TableExampleAssembly.swift in Sources */, 627 | 318EB7ED1E5A44610072426E /* MenuViewModel.swift in Sources */, 628 | 314A55A31E48E8BA00DD410B /* TableExampleCell.swift in Sources */, 629 | ); 630 | runOnlyForDeploymentPostprocessing = 0; 631 | }; 632 | 318DD1691E5CDA9100F1A091 /* Sources */ = { 633 | isa = PBXSourcesBuildPhase; 634 | buildActionMask = 2147483647; 635 | files = ( 636 | 318DD17B1E5D07F600F1A091 /* SwipeToRevealViewSpec.swift in Sources */, 637 | ); 638 | runOnlyForDeploymentPostprocessing = 0; 639 | }; 640 | 31A0768A1E57C48A00D82BD7 /* Sources */ = { 641 | isa = PBXSourcesBuildPhase; 642 | buildActionMask = 2147483647; 643 | files = ( 644 | 31D017871E5BB15800F89A35 /* TableExampleViewControllerSpec.swift in Sources */, 645 | 315BBE071E5BA976006148B7 /* NavigationControllerSpy.swift in Sources */, 646 | 31A076981E57C6E100D82BD7 /* AppDelegateSpec.swift in Sources */, 647 | 318EB7EB1E5A43D70072426E /* MenuViewControllerSpec.swift in Sources */, 648 | 31D0178A1E5BB76300F89A35 /* TableExampleCellSpec.swift in Sources */, 649 | 31A076A31E57CD8A00D82BD7 /* WindowSpy.swift in Sources */, 650 | 311714D81E5A6B640029A012 /* MainMenuItemViewModelSpec.swift in Sources */, 651 | 311714DB1E5A6D5C0029A012 /* MainMenuViewModelSpec.swift in Sources */, 652 | ); 653 | runOnlyForDeploymentPostprocessing = 0; 654 | }; 655 | /* End PBXSourcesBuildPhase section */ 656 | 657 | /* Begin PBXTargetDependency section */ 658 | 31A076A01E57C8D900D82BD7 /* PBXTargetDependency */ = { 659 | isa = PBXTargetDependency; 660 | target = 314A55891E48E1CC00DD410B /* SwipeToRevealExample */; 661 | targetProxy = 31A0769F1E57C8D900D82BD7 /* PBXContainerItemProxy */; 662 | }; 663 | /* End PBXTargetDependency section */ 664 | 665 | /* Begin PBXVariantGroup section */ 666 | 314A55961E48E1CC00DD410B /* LaunchScreen.storyboard */ = { 667 | isa = PBXVariantGroup; 668 | children = ( 669 | 314A55971E48E1CC00DD410B /* Base */, 670 | ); 671 | name = LaunchScreen.storyboard; 672 | path = .; 673 | sourceTree = ""; 674 | }; 675 | /* End PBXVariantGroup section */ 676 | 677 | /* Begin XCBuildConfiguration section */ 678 | 314A559A1E48E1CC00DD410B /* Debug */ = { 679 | isa = XCBuildConfiguration; 680 | buildSettings = { 681 | ALWAYS_SEARCH_USER_PATHS = NO; 682 | CLANG_ANALYZER_NONNULL = YES; 683 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 684 | CLANG_CXX_LIBRARY = "libc++"; 685 | CLANG_ENABLE_MODULES = YES; 686 | CLANG_ENABLE_OBJC_ARC = YES; 687 | CLANG_WARN_BOOL_CONVERSION = YES; 688 | CLANG_WARN_CONSTANT_CONVERSION = YES; 689 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 690 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 691 | CLANG_WARN_EMPTY_BODY = YES; 692 | CLANG_WARN_ENUM_CONVERSION = YES; 693 | CLANG_WARN_INFINITE_RECURSION = YES; 694 | CLANG_WARN_INT_CONVERSION = YES; 695 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 696 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 697 | CLANG_WARN_UNREACHABLE_CODE = YES; 698 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 699 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 700 | COPY_PHASE_STRIP = NO; 701 | DEBUG_INFORMATION_FORMAT = dwarf; 702 | ENABLE_STRICT_OBJC_MSGSEND = YES; 703 | ENABLE_TESTABILITY = YES; 704 | GCC_C_LANGUAGE_STANDARD = gnu99; 705 | GCC_DYNAMIC_NO_PIC = NO; 706 | GCC_NO_COMMON_BLOCKS = YES; 707 | GCC_OPTIMIZATION_LEVEL = 0; 708 | GCC_PREPROCESSOR_DEFINITIONS = ( 709 | "DEBUG=1", 710 | "$(inherited)", 711 | ); 712 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 713 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 714 | GCC_WARN_UNDECLARED_SELECTOR = YES; 715 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 716 | GCC_WARN_UNUSED_FUNCTION = YES; 717 | GCC_WARN_UNUSED_VARIABLE = YES; 718 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 719 | MTL_ENABLE_DEBUG_INFO = YES; 720 | ONLY_ACTIVE_ARCH = YES; 721 | OTHER_SWIFT_FLAGS = "-Onone -Xfrontend -debug-time-function-bodies"; 722 | SDKROOT = iphoneos; 723 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 724 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 725 | }; 726 | name = Debug; 727 | }; 728 | 314A559B1E48E1CC00DD410B /* Release */ = { 729 | isa = XCBuildConfiguration; 730 | buildSettings = { 731 | ALWAYS_SEARCH_USER_PATHS = NO; 732 | CLANG_ANALYZER_NONNULL = YES; 733 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 734 | CLANG_CXX_LIBRARY = "libc++"; 735 | CLANG_ENABLE_MODULES = YES; 736 | CLANG_ENABLE_OBJC_ARC = YES; 737 | CLANG_WARN_BOOL_CONVERSION = YES; 738 | CLANG_WARN_CONSTANT_CONVERSION = YES; 739 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 740 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 741 | CLANG_WARN_EMPTY_BODY = YES; 742 | CLANG_WARN_ENUM_CONVERSION = YES; 743 | CLANG_WARN_INFINITE_RECURSION = YES; 744 | CLANG_WARN_INT_CONVERSION = YES; 745 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 746 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 747 | CLANG_WARN_UNREACHABLE_CODE = YES; 748 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 749 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 750 | COPY_PHASE_STRIP = NO; 751 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 752 | ENABLE_NS_ASSERTIONS = NO; 753 | ENABLE_STRICT_OBJC_MSGSEND = YES; 754 | GCC_C_LANGUAGE_STANDARD = gnu99; 755 | GCC_NO_COMMON_BLOCKS = YES; 756 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 757 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 758 | GCC_WARN_UNDECLARED_SELECTOR = YES; 759 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 760 | GCC_WARN_UNUSED_FUNCTION = YES; 761 | GCC_WARN_UNUSED_VARIABLE = YES; 762 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 763 | MTL_ENABLE_DEBUG_INFO = NO; 764 | SDKROOT = iphoneos; 765 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 766 | VALIDATE_PRODUCT = YES; 767 | }; 768 | name = Release; 769 | }; 770 | 314A559D1E48E1CC00DD410B /* Debug */ = { 771 | isa = XCBuildConfiguration; 772 | baseConfigurationReference = 965BBCD85442586E700FFB28 /* Pods-SwipeToRevealExample.debug.xcconfig */; 773 | buildSettings = { 774 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 775 | DEVELOPMENT_TEAM = ""; 776 | INFOPLIST_FILE = Example/Application/Info.plist; 777 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 778 | PRODUCT_BUNDLE_IDENTIFIER = pl.darrarski.SwipeToRevealExample; 779 | PRODUCT_NAME = "$(TARGET_NAME)"; 780 | SWIFT_VERSION = 3.0; 781 | }; 782 | name = Debug; 783 | }; 784 | 314A559E1E48E1CC00DD410B /* Release */ = { 785 | isa = XCBuildConfiguration; 786 | baseConfigurationReference = 1EBACFF15A65C0ED772D25B3 /* Pods-SwipeToRevealExample.release.xcconfig */; 787 | buildSettings = { 788 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 789 | DEVELOPMENT_TEAM = ""; 790 | INFOPLIST_FILE = Example/Application/Info.plist; 791 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 792 | PRODUCT_BUNDLE_IDENTIFIER = pl.darrarski.SwipeToRevealExample; 793 | PRODUCT_NAME = "$(TARGET_NAME)"; 794 | SWIFT_VERSION = 3.0; 795 | }; 796 | name = Release; 797 | }; 798 | 318DD1721E5CDA9100F1A091 /* Debug */ = { 799 | isa = XCBuildConfiguration; 800 | baseConfigurationReference = 0598EC71861C2CB0F06509AC /* Pods-SwipeToRevealTests.debug.xcconfig */; 801 | buildSettings = { 802 | INFOPLIST_FILE = Tests/Info.plist; 803 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 804 | PRODUCT_BUNDLE_IDENTIFIER = pl.darrarski.SwipeToRevealTests; 805 | PRODUCT_NAME = "$(TARGET_NAME)"; 806 | SWIFT_VERSION = 3.0; 807 | }; 808 | name = Debug; 809 | }; 810 | 318DD1731E5CDA9100F1A091 /* Release */ = { 811 | isa = XCBuildConfiguration; 812 | baseConfigurationReference = E08F4E7EEFE7CC457E74E83D /* Pods-SwipeToRevealTests.release.xcconfig */; 813 | buildSettings = { 814 | INFOPLIST_FILE = Tests/Info.plist; 815 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 816 | PRODUCT_BUNDLE_IDENTIFIER = pl.darrarski.SwipeToRevealTests; 817 | PRODUCT_NAME = "$(TARGET_NAME)"; 818 | SWIFT_VERSION = 3.0; 819 | }; 820 | name = Release; 821 | }; 822 | 31A076941E57C48A00D82BD7 /* Debug */ = { 823 | isa = XCBuildConfiguration; 824 | baseConfigurationReference = 5B4979947EEC3481468A09BD /* Pods-SwipeToRevealExampleTests.debug.xcconfig */; 825 | buildSettings = { 826 | BUNDLE_LOADER = "$(TEST_HOST)"; 827 | DEVELOPMENT_TEAM = ""; 828 | INFOPLIST_FILE = ExampleTests/Info.plist; 829 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 830 | PRODUCT_BUNDLE_IDENTIFIER = pl.darrarski.SwipeToRevealExampleTests; 831 | PRODUCT_NAME = "$(TARGET_NAME)"; 832 | SWIFT_VERSION = 3.0; 833 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwipeToRevealExample.app/SwipeToRevealExample"; 834 | }; 835 | name = Debug; 836 | }; 837 | 31A076951E57C48A00D82BD7 /* Release */ = { 838 | isa = XCBuildConfiguration; 839 | baseConfigurationReference = F736ACAA48911E20D6BD4B89 /* Pods-SwipeToRevealExampleTests.release.xcconfig */; 840 | buildSettings = { 841 | BUNDLE_LOADER = "$(TEST_HOST)"; 842 | DEVELOPMENT_TEAM = ""; 843 | INFOPLIST_FILE = ExampleTests/Info.plist; 844 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 845 | PRODUCT_BUNDLE_IDENTIFIER = pl.darrarski.SwipeToRevealExampleTests; 846 | PRODUCT_NAME = "$(TARGET_NAME)"; 847 | SWIFT_VERSION = 3.0; 848 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwipeToRevealExample.app/SwipeToRevealExample"; 849 | }; 850 | name = Release; 851 | }; 852 | /* End XCBuildConfiguration section */ 853 | 854 | /* Begin XCConfigurationList section */ 855 | 314A55851E48E1CC00DD410B /* Build configuration list for PBXProject "SwipeToReveal" */ = { 856 | isa = XCConfigurationList; 857 | buildConfigurations = ( 858 | 314A559A1E48E1CC00DD410B /* Debug */, 859 | 314A559B1E48E1CC00DD410B /* Release */, 860 | ); 861 | defaultConfigurationIsVisible = 0; 862 | defaultConfigurationName = Release; 863 | }; 864 | 314A559C1E48E1CC00DD410B /* Build configuration list for PBXNativeTarget "SwipeToRevealExample" */ = { 865 | isa = XCConfigurationList; 866 | buildConfigurations = ( 867 | 314A559D1E48E1CC00DD410B /* Debug */, 868 | 314A559E1E48E1CC00DD410B /* Release */, 869 | ); 870 | defaultConfigurationIsVisible = 0; 871 | defaultConfigurationName = Release; 872 | }; 873 | 318DD1741E5CDA9100F1A091 /* Build configuration list for PBXNativeTarget "SwipeToRevealTests" */ = { 874 | isa = XCConfigurationList; 875 | buildConfigurations = ( 876 | 318DD1721E5CDA9100F1A091 /* Debug */, 877 | 318DD1731E5CDA9100F1A091 /* Release */, 878 | ); 879 | defaultConfigurationIsVisible = 0; 880 | defaultConfigurationName = Release; 881 | }; 882 | 31A076931E57C48A00D82BD7 /* Build configuration list for PBXNativeTarget "SwipeToRevealExampleTests" */ = { 883 | isa = XCConfigurationList; 884 | buildConfigurations = ( 885 | 31A076941E57C48A00D82BD7 /* Debug */, 886 | 31A076951E57C48A00D82BD7 /* Release */, 887 | ); 888 | defaultConfigurationIsVisible = 0; 889 | defaultConfigurationName = Release; 890 | }; 891 | /* End XCConfigurationList section */ 892 | }; 893 | rootObject = 314A55821E48E1CC00DD410B /* Project object */; 894 | } 895 | -------------------------------------------------------------------------------- /SwipeToReveal.xcodeproj/xcshareddata/xcschemes/SwipeToReveal Example Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 104 | 105 | 106 | 107 | 109 | 110 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /SwipeToReveal.xcodeproj/xcshareddata/xcschemes/SwipeToReveal Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SwipeToReveal.xcodeproj/xcshareddata/xcschemes/SwipeToReveal Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /SwipeToReveal.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/SwipeToRevealViewSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import SwipeToReveal 4 | 5 | class SwipeToRevealViewSpec: QuickSpec { 6 | 7 | override func spec() { 8 | context("init with coder") { 9 | it("should throw asserion") { 10 | expect { () -> Void in _ = SwipeToRevealView(coder: NSCoder()) }.to(throwAssertion()) 11 | } 12 | } 13 | 14 | context("init") { 15 | var sut: SwipeToRevealView! 16 | var size: CGSize! 17 | 18 | beforeEach { 19 | size = CGSize(width: 100, height: 20) 20 | sut = SwipeToRevealView(frame: .zero) 21 | sut.translatesAutoresizingMaskIntoConstraints = false 22 | sut.widthAnchor.constraint(equalToConstant: size.width).isActive = true 23 | sut.heightAnchor.constraint(equalToConstant: size.height).isActive = true 24 | sut.setNeedsLayout() 25 | sut.layoutIfNeeded() 26 | } 27 | 28 | it("should not have content view") { 29 | expect(sut.contentView).to(beNil()) 30 | } 31 | 32 | it("should not have right view") { 33 | expect(sut.rightView).to(beNil()) 34 | } 35 | 36 | it("should have correct size") { 37 | expect(sut.frame.size).to(equal(size)) 38 | } 39 | 40 | context("set content view") { 41 | var contentView: UIView! 42 | 43 | beforeEach { 44 | contentView = UIView(frame: .zero) 45 | sut.contentView = contentView 46 | sut.setNeedsLayout() 47 | sut.layoutIfNeeded() 48 | } 49 | 50 | it("should have correct content view") { 51 | expect(sut.contentView).to(be(contentView)) 52 | } 53 | 54 | it("should content view have superview") { 55 | expect(contentView.superview).notTo(beNil()) 56 | } 57 | 58 | it("should content view have correct size") { 59 | expect(contentView.frame.size).to(equal(sut.frame.size)) 60 | } 61 | 62 | it("should content view have correct origin") { 63 | let origin = contentView.convert(contentView.frame.origin, to: sut) 64 | expect(origin).to(equal(CGPoint.zero)) 65 | } 66 | 67 | context("change content view") { 68 | var newContentView: UIView! 69 | 70 | beforeEach { 71 | newContentView = UIView(frame: .zero) 72 | sut.contentView = newContentView 73 | sut.setNeedsLayout() 74 | sut.layoutIfNeeded() 75 | } 76 | 77 | it("should have correct content view") { 78 | expect(sut.contentView).to(be(newContentView)) 79 | } 80 | 81 | it("should new content view have superview") { 82 | expect(newContentView.superview).notTo(beNil()) 83 | } 84 | 85 | it("should previous content view not have superview") { 86 | expect(contentView.superview).to(beNil()) 87 | } 88 | } 89 | 90 | context("set right view") { 91 | var rightView: UIView! 92 | var rightViewWidth: CGFloat! 93 | 94 | beforeEach { 95 | rightViewWidth = 30 96 | rightView = UIView(frame: .zero) 97 | rightView.translatesAutoresizingMaskIntoConstraints = false 98 | rightView.widthAnchor.constraint(equalToConstant: rightViewWidth).isActive = true 99 | sut.rightView = rightView 100 | sut.setNeedsLayout() 101 | sut.layoutIfNeeded() 102 | } 103 | 104 | it("should have correct right view") { 105 | expect(sut.rightView).to(be(rightView)) 106 | } 107 | 108 | it("should right view have superview") { 109 | expect(rightView.superview).notTo(beNil()) 110 | } 111 | 112 | it("should right view have correct size") { 113 | expect(rightView.frame.width).to(equal(rightViewWidth)) 114 | expect(rightView.frame.height).to(equal(sut.bounds.height)) 115 | } 116 | 117 | it("should right view have correct origin") { 118 | let origin = rightView.convert(rightView.frame.origin, to: sut) 119 | let expectation = CGPoint(x: sut.bounds.maxX, y: sut.bounds.minY) 120 | expect(origin).to(equal(expectation)) 121 | } 122 | 123 | context("change right view") { 124 | var newRightView: UIView! 125 | 126 | beforeEach { 127 | newRightView = UIView(frame: .zero) 128 | sut.rightView = newRightView 129 | sut.setNeedsLayout() 130 | sut.layoutIfNeeded() 131 | } 132 | 133 | it("should have correct right view") { 134 | expect(sut.rightView).to(be(newRightView)) 135 | } 136 | 137 | it("should new right view have superview") { 138 | expect(newRightView.superview).notTo(beNil()) 139 | } 140 | 141 | it("should previous right view not have superview") { 142 | expect(rightView.superview).to(beNil()) 143 | } 144 | } 145 | 146 | context("reveal right") { 147 | beforeEach { 148 | sut.revealRight(animated: false) 149 | sut.setNeedsLayout() 150 | sut.layoutIfNeeded() 151 | } 152 | 153 | it("should right view have correct size") { 154 | expect(rightView.frame.width).to(equal(rightViewWidth)) 155 | expect(rightView.frame.height).to(equal(sut.bounds.height)) 156 | } 157 | 158 | it("should right view have correct origin") { 159 | let origin = rightView.convert(rightView.frame.origin, to: sut) 160 | let expectation = CGPoint( 161 | x: sut.bounds.maxX - rightView.frame.width, 162 | y: sut.bounds.minY 163 | ) 164 | expect(origin).to(equal(expectation)) 165 | } 166 | 167 | context("close") { 168 | beforeEach { 169 | sut.close(animated: false) 170 | sut.setNeedsLayout() 171 | sut.layoutIfNeeded() 172 | } 173 | 174 | it("should right view have correct size") { 175 | expect(rightView.frame.width).to(equal(rightViewWidth)) 176 | expect(rightView.frame.height).to(equal(sut.bounds.height)) 177 | } 178 | 179 | it("should right view have correct origin") { 180 | let origin = rightView.convert(rightView.frame.origin, to: sut) 181 | let expectation = CGPoint(x: sut.bounds.maxX, y: sut.bounds.minY) 182 | expect(origin).to(equal(expectation)) 183 | } 184 | } 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | } 192 | --------------------------------------------------------------------------------