diff --git a/PagingMenuController.xcodeproj/project.pbxproj b/PagingMenuController.xcodeproj/project.pbxproj index 24d3a90e..963bc06c 100644 --- a/PagingMenuController.xcodeproj/project.pbxproj +++ b/PagingMenuController.xcodeproj/project.pbxproj @@ -132,7 +132,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0640; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = kitasuke; TargetAttributes = { ECE530661B6DEB18001CF201 = { @@ -194,13 +194,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -208,6 +218,7 @@ CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -243,13 +254,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -269,6 +290,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -281,6 +303,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -289,10 +312,11 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.yusuke.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -300,6 +324,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -308,9 +333,10 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.yusuke.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/PagingMenuController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PagingMenuController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/PagingMenuController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PagingMenuController.xcodeproj/xcshareddata/xcschemes/PagingMenuController.xcscheme b/PagingMenuController.xcodeproj/xcshareddata/xcschemes/PagingMenuController.xcscheme index 4a4595cd..a9d5489f 100644 --- a/PagingMenuController.xcodeproj/xcshareddata/xcschemes/PagingMenuController.xcscheme +++ b/PagingMenuController.xcodeproj/xcshareddata/xcschemes/PagingMenuController.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:PagingMenuController.xcodeproj"> + + CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.yusuke.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Pod/Classes/MenuItemView.swift b/Pod/Classes/MenuItemView.swift index 44e92278..54827c7c 100644 --- a/Pod/Classes/MenuItemView.swift +++ b/Pod/Classes/MenuItemView.swift @@ -58,7 +58,7 @@ open class MenuItemView: UIView { return imageView }() - fileprivate var menuOptions: MenuViewCustomizable! + fileprivate(set) var menuOptions: MenuViewCustomizable! fileprivate var menuItemOptions: MenuItemViewCustomizable! fileprivate var widthConstraint: NSLayoutConstraint! fileprivate var descriptionWidthConstraint: NSLayoutConstraint! @@ -303,7 +303,7 @@ extension MenuItemView { fileprivate func estimatedLabelSize(_ label: UILabel) -> CGSize { guard let text = label.text else { return .zero } - return NSString(string: text).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: label.font], context: nil).size + return NSString(string: text).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [kCTFontAttributeName as NSAttributedStringKey: label.font], context: nil).size } fileprivate func calculateLabelSize(_ label: UILabel, maxWidth: CGFloat) -> CGSize { diff --git a/Pod/Classes/MenuView.swift b/Pod/Classes/MenuView.swift index 6eba4054..de1bf445 100644 --- a/Pod/Classes/MenuView.swift +++ b/Pod/Classes/MenuView.swift @@ -11,10 +11,10 @@ import UIKit open class MenuView: UIScrollView { public fileprivate(set) var currentMenuItemView: MenuItemView! - internal fileprivate(set) var menuItemViews = [MenuItemView]() + public fileprivate(set) var menuItemViews = [MenuItemView]() internal var onMove: ((MenuMoveState) -> Void)? - fileprivate var menuOptions: MenuViewCustomizable! + public var menuOptions: MenuViewCustomizable! fileprivate var sortedMenuItemViews = [MenuItemView]() fileprivate let contentView: UIView = { $0.translatesAutoresizingMaskIntoConstraints = false @@ -30,16 +30,29 @@ open class MenuView: UIScrollView { fileprivate var menuViewBounces: Bool { switch menuOptions.displayMode { case .standard(_, _, .scrollEnabledAndBouces), - .infinite(_, .scrollEnabledAndBouces): return true + .standard(_, _, .freeScroll), + .infinite(_, .scrollEnabledAndBouces), + .infinite(_, .freeScroll): return true default: return false } } + var freescroll:Bool { + switch menuOptions.displayMode { + case .standard(_, _, .freeScroll), + .infinite(_, .freeScroll): + return true + default: + return false + } + } fileprivate var menuViewScrollEnabled: Bool { switch menuOptions.displayMode { case .standard(_, _, .scrollEnabledAndBouces), .standard(_, _, .scrollEnabled), + .standard(_, _, .freeScroll), .infinite(_, .scrollEnabledAndBouces), - .infinite(_, .scrollEnabled): return true + .infinite(_, .scrollEnabled), + .infinite(_, .freeScroll): return true default: return false } } @@ -78,7 +91,7 @@ open class MenuView: UIScrollView { fileprivate var currentIndex: Int = 0 // MARK: - Lifecycle - internal init(menuOptions: MenuViewCustomizable) { + public init(menuOptions: MenuViewCustomizable) { super.init(frame: CGRect(x: 0, y: 0, width: 0, height: menuOptions.height)) self.menuOptions = menuOptions @@ -108,8 +121,43 @@ open class MenuView: UIScrollView { } // MARK: - Internal method + public func scrollBetween(toPage: Int, percent:CGFloat){ + guard toPage >= 0 && toPage < menuItemViews.count else { + return + } + guard case .underline(_, _, let horizontalPadding, _) = menuOptions.focusMode else { return } + let previousMenuItem = menuItemViews[currentPage] + let previousFrame = previousMenuItem.frame + let targetMenuItem = menuItemViews[toPage] + let targetFrame = targetMenuItem.frame + let differencePos = ((targetFrame.minX - previousFrame.minX)*percent) + let differenceSize = (previousFrame.width*(1-percent) + targetFrame.width*percent) + underlineView.frame.origin.x = previousFrame.minX + differencePos + horizontalPadding + underlineView.frame.size.width = differenceSize - horizontalPadding * 2 + + guard case .text(let title) = menuOptions.itemsOptions[0].displayMode else { return } + previousMenuItem.titleLabel.textColor = addColor(multiplyColor(title.selectedColor, by: (1-percent)), with: multiplyColor(title.color, by: percent)) + targetMenuItem.titleLabel.textColor = addColor(multiplyColor(title.selectedColor, by: percent), with: multiplyColor(title.color, by: (1-percent))) + } + ///simply put coloe function in here. + private func addColor(_ color1: UIColor, with color2: UIColor) -> UIColor { + var (r1, g1, b1, a1) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0)) + var (r2, g2, b2, a2) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0)) + + color1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) + color2.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) + + // add the components, but don't let them go above 1.0 + return UIColor(red: min(r1 + r2, 1), green: min(g1 + g2, 1), blue: min(b1 + b2, 1), alpha: (a1 + a2) / 2) + } - internal func move(toPage page: Int, animated: Bool = true) { + private func multiplyColor(_ color: UIColor, by multiplier: CGFloat) -> UIColor { + var (r, g, b, a) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0)) + color.getRed(&r, green: &g, blue: &b, alpha: &a) + return UIColor(red: r * multiplier, green: g * multiplier, blue: b * multiplier, alpha: a) + } + + public func move(toPage page: Int, animated: Bool = true) { // hide menu view when constructing itself if !animated { alpha = 0 @@ -129,9 +177,7 @@ open class MenuView: UIScrollView { let duration = animated ? menuOptions.animationDuration : 0 UIView.animate(withDuration: duration, animations: { [unowned self] () -> Void in self.focusMenuItem() - if self.menuOptions.selectedItemCenter { - self.positionMenuItemViews() - } + self.positionMenuItemViews() }) { [weak self] (_) in guard let _ = self else { return } @@ -139,9 +185,8 @@ open class MenuView: UIScrollView { if case .infinite = self!.menuOptions.displayMode { self!.relayoutMenuItemViews() } - if self!.menuOptions.selectedItemCenter { - self!.positionMenuItemViews() - } + self!.positionMenuItemViews() + self!.setNeedsLayout() self!.layoutIfNeeded() @@ -163,7 +208,7 @@ open class MenuView: UIScrollView { } setNeedsLayout() layoutIfNeeded() - + animateUnderlineViewIfNeeded() animateRoundRectViewIfNeeded() } @@ -205,7 +250,7 @@ open class MenuView: UIScrollView { contentView.heightAnchor.constraint(equalTo: heightAnchor) ]) } - + fileprivate func constructMenuItemViews(_ menuOptions: MenuViewCustomizable) { constructMenuItemViews({ return MenuItemView(menuOptions: menuOptions, menuItemOptions: menuOptions.itemsOptions[$0], addDiveder: $1) @@ -302,14 +347,17 @@ open class MenuView: UIScrollView { roundRectView.frame.origin.x = targetFrame.minX + horizontalPadding roundRectView.frame.size.width = targetFrame.width - horizontalPadding * 2 } - + fileprivate func relayoutMenuItemViews() { sortMenuItemViews() layoutMenuItemViews() } - + fileprivate func positionMenuItemViews() { - contentOffset.x = contentOffsetX + let item = self.currentMenuItemView.frame + if self.menuOptions.selectedItemCenter || !bounds.contains(item) { + contentOffset.x = contentOffsetX + } animateUnderlineViewIfNeeded() animateRoundRectViewIfNeeded() } @@ -340,7 +388,7 @@ open class MenuView: UIScrollView { self.currentMenuItemView = $0 } } - + // make selected item foreground sortedMenuItemViews.forEach { $0.layer.zPosition = isSelected($0) ? 0 : -1 } @@ -350,13 +398,13 @@ open class MenuView: UIScrollView { } extension MenuView: Pagable { - var currentPage: Int { + public var currentPage: Int { return currentIndex } - var previousPage: Int { + public var previousPage: Int { return currentPage - 1 < 0 ? menuItemCount - 1 : currentPage - 1 } - var nextPage: Int { + public var nextPage: Int { return currentPage + 1 > menuItemCount - 1 ? 0 : currentPage + 1 } func update(currentPage page: Int) { diff --git a/Pod/Classes/PagingMenuController.swift b/Pod/Classes/PagingMenuController.swift index e8344a5f..a5d9a9b7 100644 --- a/Pod/Classes/PagingMenuController.swift +++ b/Pod/Classes/PagingMenuController.swift @@ -16,6 +16,7 @@ public enum MenuMoveState { case didMoveController(to: UIViewController, from: UIViewController) case willMoveItem(to: MenuItemView, from: MenuItemView) case didMoveItem(to: MenuItemView, from: MenuItemView) + case scrollingView(toPage:Int, percent:CGFloat) case didScrollStart case didScrollEnd } @@ -24,6 +25,8 @@ internal let MinimumSupportedViewCount = 1 internal let VisiblePagingViewNumber = 3 open class PagingMenuController: UIViewController { + var initialOffset:CGPoint = .zero + public fileprivate(set) var menuView: MenuView? { didSet { guard let menuView = menuView else { return } @@ -138,8 +141,9 @@ open class PagingMenuController: UIViewController { constructPagingViewController() layoutPagingViewController() } - + var movingPageAutomatically = false open func move(toPage page: Int, animated: Bool = true) { + movingPageAutomatically = true switch options.componentType { case .menuView, .all: // ignore an unexpected page number @@ -184,11 +188,12 @@ open class PagingMenuController: UIViewController { } let completionClosure = { [weak self] (_: Bool) -> Void in pagingViewController.relayoutPagingViewControllers() - + // show paging views self?.showPagingMenuControllers() - + self?.onMove?(.didMoveController(to: nextPagingViewController, from: previousPagingViewController)) + self?.movingPageAutomatically = false } if duration > 0 { UIView.animate(withDuration: duration, animations: animationClosure, completion: completionClosure) @@ -242,7 +247,7 @@ open class PagingMenuController: UIViewController { menuView.setNeedsLayout() menuView.layoutIfNeeded() } - + fileprivate func constructPagingViewController() { let viewControllers: [UIViewController] switch options.componentType { @@ -253,7 +258,7 @@ open class PagingMenuController: UIViewController { pagingViewController = PagingViewController(viewControllers: viewControllers, options: options) } - + fileprivate func layoutPagingViewController() { guard let pagingViewController = pagingViewController else { return } @@ -314,31 +319,52 @@ extension PagingMenuController: UIScrollViewDelegate { public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { onMove?(.didScrollEnd) + var contentScrollView:Bool = false let nextPage: Int switch (scrollView, pagingViewController, menuView) { case let (scrollView, pagingViewController?, _) where scrollView.isEqual(pagingViewController.contentScrollView): nextPage = nextPageFromCurrentPosition + contentScrollView = true case let (scrollView, _, menuView?) where scrollView.isEqual(menuView): nextPage = nextPageFromCurrentPoint default: return } - - move(toPage: nextPage) + if !(menuView?.freescroll ?? false) || contentScrollView { + move(toPage: nextPage) + } } - + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.initialOffset = scrollView.contentOffset + self.movingPageAutomatically = false onMove?(.didScrollStart) } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { switch (scrollView, decelerate) { - case (let scrollView, false) where scrollView.isEqual(menuView): break + case (let scrollView, false) where scrollView.isEqual(menuView) && !(menuView?.freescroll ?? false): break default: return } let nextPage = nextPageFromCurrentPoint move(toPage: nextPage) } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + guard scrollView != self.menuView else {return} + let percent:CGFloat + let page:Int + if initialOffset.x > scrollView.contentOffset.x { + page = movingPageAutomatically ? currentPage : currentPage - 1 + percent = (initialOffset.x - scrollView.contentOffset.x)/self.view.frame.width + }else { + page = movingPageAutomatically ? currentPage : currentPage + 1 + percent = (scrollView.contentOffset.x - initialOffset.x)/self.view.frame.width + } + // print(page, percent) + menuView?.scrollBetween(toPage: page, percent: percent) + onMove?(.scrollingView(toPage: page, percent:percent)) + } } extension PagingMenuController: Pagable { @@ -478,7 +504,7 @@ extension PagingMenuController { menuView?.addGestureRecognizer(rightSwipeGestureRecognizer) } - internal func handleTapGesture(_ recognizer: UITapGestureRecognizer) { + @objc internal func handleTapGesture(_ recognizer: UITapGestureRecognizer) { guard let menuItemView = recognizer.view as? MenuItemView, let menuView = menuView, let page = menuView.menuItemViews.index(of: menuItemView), @@ -503,7 +529,7 @@ extension PagingMenuController { move(toPage: newPage) } - internal func handleSwipeGesture(_ recognizer: UISwipeGestureRecognizer) { + @objc internal func handleSwipeGesture(_ recognizer: UISwipeGestureRecognizer) { guard let menuView = recognizer.view as? MenuView, let menuOptions = menuOptions else { return } diff --git a/Pod/Classes/Protocols/MenuViewCustomizable.swift b/Pod/Classes/Protocols/MenuViewCustomizable.swift index 376006b0..07db5d3c 100644 --- a/Pod/Classes/Protocols/MenuViewCustomizable.swift +++ b/Pod/Classes/Protocols/MenuViewCustomizable.swift @@ -74,6 +74,7 @@ public enum MenuScrollingMode { case scrollEnabled case scrollEnabledAndBouces case pagingEnabled + case freeScroll } public enum MenuFocusMode {