From 549793a17b1dd8eccbf9cdce5729637e78a395ca Mon Sep 17 00:00:00 2001 From: Ivan Yordanov Date: Wed, 5 Oct 2016 16:34:55 +0400 Subject: [PATCH 1/3] Adding animate to top method --- SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift | 7 +++++++ .../Classes/SJSegmentedViewController.swift | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift b/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift index f4a7430..c4fea45 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift @@ -22,6 +22,13 @@ import UIKit +extension UIScrollView { + func scrollToTop(height: CGFloat) { + let desiredOffset = CGPoint(x: 0, y: contentInset.top + height) + setContentOffset(desiredOffset, animated: true) + } +} + class SJSegmentedScrollView: UIScrollView { var segmentView: SJSegmentView? diff --git a/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift b/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift index 6c0e9dd..051d76d 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift @@ -414,5 +414,13 @@ import UIKit segment: segment, index: 0) } + + /** + * Method for atuomaticly scroll the segmet controller on top when button is taped + **/ + + open func animateToTop() { + segmentedScrollView.scrollToTop(height: headerViewHeight) + } } From 846c6d5c9af68d01a818d44213b548d9fe90573c Mon Sep 17 00:00:00 2001 From: Ivan Yordanov Date: Tue, 18 Oct 2016 10:33:33 +0400 Subject: [PATCH 2/3] add pull to refresh from tableView --- .../contents.xcworkspacedata | 7 + .../Classes/SJSegmentView.swift | 357 ++++++++++++++++++ .../Classes/SJSegmentedScrollView.swift | 78 ++-- .../Classes/SJSegmentedViewController.swift | 28 +- 4 files changed, 432 insertions(+), 38 deletions(-) create mode 100644 Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SJSegmentedScrollView/Classes/SJSegmentView.swift b/SJSegmentedScrollView/Classes/SJSegmentView.swift index 6de3345..c23ef36 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentView.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentView.swift @@ -19,6 +19,363 @@ // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// SJSegmentView.swift +// SJSegmentedScrollView +// +// Created by Subins Jose on 10/06/16. +// Copyright © 2016 Subins Jose. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import UIKit + +typealias DidSelectSegmentAtIndex = (_ segment:UIButton?, _ index: Int) -> Void + +class SJSegmentView: UIScrollView { + + var selectedSegmentViewColor: UIColor? { + didSet { + selectedSegmentView?.backgroundColor = selectedSegmentViewColor + } + } + + var titleColor: UIColor? { + didSet { + for segment in segments { + segment.setTitleColor(titleColor, for: UIControlState()) + } + } + } + + var segmentBackgroundColor: UIColor? { + didSet { + for segment in segments { + segment.backgroundColor = segmentBackgroundColor + } + } + } + + var shadow: SJShadow? { + didSet { + if let shadow = shadow { + layer.shadowOffset = shadow.offset + layer.shadowColor = shadow.color.cgColor + layer.shadowRadius = shadow.radius + layer.shadowOpacity = shadow.opacity + layer.masksToBounds = false; + } + } + } + + var font: UIFont? + var selectedSegmentViewHeight: CGFloat? + let kSegmentViewTagOffset = 100 + var segmentViewOffsetWidth: CGFloat = 10.0 + var titles: [String]? + var segments = [UIButton]() + var segmentContentView: UIView? + var didSelectSegmentAtIndex: DidSelectSegmentAtIndex? + var selectedSegmentView: UIView? + var xPosConstraints: NSLayoutConstraint? + var contentViewWidthConstraint: NSLayoutConstraint? + var selectedSegmentViewWidthConstraint: NSLayoutConstraint? + var contentSubViewWidthConstraints = [NSLayoutConstraint]() + + var contentView: SJContentView? { + didSet { + contentView!.addObserver(self, + forKeyPath: "contentOffset", + options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old], + context: nil) + } + } + + convenience init(frame: CGRect, segmentTitles: [String]) { + self.init(frame: frame) + + titles = segmentTitles + showsHorizontalScrollIndicator = false + showsVerticalScrollIndicator = false + bounces = false + + + NotificationCenter.default.addObserver(self, + selector: #selector(SJSegmentView.didChangeSegmentIndex(_:)), + name: NSNotification.Name("DidChangeSegmentIndex"), + object: nil) + } + + required override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + contentView!.removeObserver(self, + forKeyPath: "contentOffset", + context: nil) + + NotificationCenter.default.removeObserver(self, + name:NSNotification.Name("DidChangeSegmentIndex"), + object: nil) + } + + func didChangeSegmentIndex(_ notification: Notification) { + + //deselect previous buttons + for button in segments { + button.isSelected = false + } + + // select current button + let index = notification.object as? Int + let button = segments[index!] + button.isSelected = true + + } + + + func setSegmentsView(_ frame: CGRect) { + + let segmentWidth = getSegmentWidth(titles!, frame: frame) + createSegmentContentView(titles!, titleWidth: segmentWidth) + + var index = 0 + for title in titles! { + + createSegmentFor(title, width: segmentWidth, index: index) + index += 1 + } + + createSelectedSegmentView(segmentWidth) + + //Set first button as selected + let button = segments.first! as UIButton + button.isSelected = true + } + + func createSegmentContentView(_ titles: [String], titleWidth: CGFloat) { + + segmentContentView = UIView(frame: CGRect.zero) + segmentContentView!.translatesAutoresizingMaskIntoConstraints = false + addSubview(segmentContentView!) + + let contentViewWidth = titleWidth * CGFloat(titles.count) + let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[contentView]|", + options: [], + metrics: nil, + views: ["contentView": segmentContentView!, + "mainView": self]) + addConstraints(horizontalConstraints) + + contentViewWidthConstraint = NSLayoutConstraint(item: segmentContentView!, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .notAnAttribute, + multiplier: 1.0, + constant: contentViewWidth) + addConstraint(contentViewWidthConstraint!) + + + let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[contentView(==mainView)]|", + options: [], + metrics: nil, + views: ["contentView": segmentContentView!, + "mainView": self]) + addConstraints(verticalConstraints) + } + + func createSegmentFor(_ title: String, width: CGFloat, index: Int) { + + let segmentView = getSegmentViewForController(title) + segmentView.tag = (index + kSegmentViewTagOffset) + segmentView.translatesAutoresizingMaskIntoConstraints = false + segmentContentView!.addSubview(segmentView) + + if segments.count == 0 { + + let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]", + options: [], + metrics: nil, + views: ["view": segmentView]) + segmentContentView!.addConstraints(horizontalConstraints) + + } else { + + let previousView = segments.last + let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[previousView]-0-[view]", + options: [], + metrics: nil, + views: ["view": segmentView, + "previousView": previousView!]) + segmentContentView!.addConstraints(horizontalConstraints) + } + + let widthConstraint = NSLayoutConstraint(item: segmentView, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .notAnAttribute, + multiplier: 1.0, + constant: width) + segmentContentView!.addConstraint(widthConstraint) + contentSubViewWidthConstraints.append(widthConstraint) + + let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|", + options: [], + metrics: nil, + views: ["view": segmentView]) + segmentContentView!.addConstraints(verticalConstraints) + + segments.append(segmentView) + } + + func createSelectedSegmentView(_ width: CGFloat) { + + let segmentView = UIView() + segmentView.backgroundColor = selectedSegmentViewColor + segmentView.translatesAutoresizingMaskIntoConstraints = false + segmentContentView!.addSubview(segmentView) + selectedSegmentView = segmentView + + xPosConstraints = NSLayoutConstraint(item: segmentView, + attribute: .leading, + relatedBy: .equal, + toItem: segmentContentView!, + attribute: .leading, + multiplier: 1.0, + constant: 0.0) + segmentContentView!.addConstraint(xPosConstraints!) + + let segment = segments.first + let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[view(==segment)]", + options: [], + metrics: nil, + views: ["view": segmentView, + "segment": segment!]) + segmentContentView!.addConstraints(horizontalConstraints) + + let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[view(height)]|", + options: [], + metrics: ["height": selectedSegmentViewHeight!], + views: ["view": segmentView]) + segmentContentView!.addConstraints(verticalConstraints) + } + + func getSegmentViewForController(_ title: String) -> UIButton { + + let button = UIButton(type: .custom) + button.backgroundColor = segmentBackgroundColor + button.setTitleColor(titleColor, for: UIControlState()) + button.setTitle(title, for: UIControlState()) + button.titleLabel?.font = font + button.addTarget(self, action: #selector(SJSegmentView.onSegmentButtonPress(_:)), + for: .touchUpInside) + return button + } + + func onSegmentButtonPress(_ sender: AnyObject) { + + let index = sender.tag - kSegmentViewTagOffset + NotificationCenter.default.post(name: Notification.Name(rawValue: "DidChangeSegmentIndex"), + object: index) + + if didSelectSegmentAtIndex != nil { + didSelectSegmentAtIndex!(sender as? UIButton, index) + } + } + + func getSegmentWidth(_ titles: [String], frame: CGRect) -> CGFloat { + + var maxWidth: CGFloat = 0 + + // find max width of segement + for title in titles { + + let string: NSString = title as NSString + let width = string.size(attributes: [NSFontAttributeName: font!]).width + + if width > maxWidth { + maxWidth = width + } + } + + let width = Int(maxWidth + segmentViewOffsetWidth) + let totalWidth = width * titles.count + if totalWidth < Int(frame.size.width) { + maxWidth = frame.size.width / CGFloat(titles.count) + } else { + maxWidth = CGFloat(width) + } + + return maxWidth + } + + override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + + if let change = change as [NSKeyValueChangeKey : AnyObject]? { + if let old = change[NSKeyValueChangeKey.oldKey], let new = change[NSKeyValueChangeKey.newKey] { + if !(old.isEqual(new)) { + //update selected segment view x position + let scrollView = object as? UIScrollView + var changeOffset = (scrollView?.contentSize.width)! / contentSize.width + let value = (scrollView?.contentOffset.x)! / changeOffset + + if !value.isNaN { + selectedSegmentView?.frame.origin.x = (scrollView?.contentOffset.x)! / changeOffset + } + + //update segment offset x position + let segmentScrollWidth = contentSize.width - bounds.width + let contentScrollWidth = scrollView!.contentSize.width - scrollView!.bounds.width + changeOffset = segmentScrollWidth / contentScrollWidth + contentOffset.x = (scrollView?.contentOffset.x)! * changeOffset + } + } + } + } + + func didChangeParentViewFrame(_ frame: CGRect) { + + let segmentWidth = getSegmentWidth(titles!, frame: frame) + let contentViewWidth = segmentWidth * CGFloat(titles!.count) + contentViewWidthConstraint?.constant = contentViewWidth + + for constraint in contentSubViewWidthConstraints { + constraint.constant = segmentWidth + } + + let changeOffset = (contentView?.contentSize.width)! / contentSize.width + let value = (contentView?.contentOffset.x)! / changeOffset + + if !value.isNaN { + xPosConstraints!.constant = (selectedSegmentView?.frame.origin.x)! + layoutIfNeeded() + } + } +} + import UIKit diff --git a/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift b/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift index c4fea45..3786535 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift @@ -53,21 +53,20 @@ class SJSegmentedScrollView: UIScrollView { var scrollContentView: UIView! var contentViewHeightConstraint: NSLayoutConstraint! var didSelectSegmentAtIndex: DidSelectSegmentAtIndex? + var tableView: UITableView? override init(frame: CGRect) { super.init(frame: frame) - sizeToFit() translatesAutoresizingMaskIntoConstraints = false showsHorizontalScrollIndicator = true - showsVerticalScrollIndicator = true - bounces = false + showsVerticalScrollIndicator = false + bounces = true + alwaysBounceVertical = true addObserver(self, forKeyPath: "contentOffset", options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old], context: nil) - - } required init?(coder aDecoder: NSCoder) { @@ -275,8 +274,13 @@ class SJSegmentedScrollView: UIScrollView { oldPosition: CGPoint) { if scrollView.contentOffset.y < 0.0 { + + if scrollView.isKind(of: UITableView.classForCoder()) { + self.tableView = scrollView as! UITableView + scrollView.isScrollEnabled = false + } + if contentOffset.y >= 0.0 { - var yPos = contentOffset.y - change yPos = yPos < 0 ? 0 : yPos let updatedPos = CGPoint(x: contentOffset.x, y: yPos) @@ -289,7 +293,6 @@ class SJSegmentedScrollView: UIScrollView { func handleScrollDown(_ scrollView: UIScrollView, change: CGFloat, oldPosition: CGPoint) { - let offset = (headerViewHeight! - headerViewOffsetHeight!) if contentOffset.y < offset { @@ -305,36 +308,45 @@ class SJSegmentedScrollView: UIScrollView { } } - override func observeValue(forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey : Any]?, - context: UnsafeMutableRawPointer?) { - if !observing { return } - - let scrollView = object as? UIScrollView - if scrollView == nil { return } - if scrollView == self { return } - - let changeValues = change as! [NSKeyValueChangeKey: AnyObject] - - if let new = changeValues[NSKeyValueChangeKey.newKey]?.cgPointValue, - let old = changeValues[NSKeyValueChangeKey.oldKey]?.cgPointValue { + override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + + if !observing { return } - let diff = old.y - new.y + let scrollView = object as? UIScrollView + if scrollView == nil { return } + + if (scrollView?.isKind(of: UIScrollView.classForCoder()))! { + if scrollView!.contentOffset.y >= headerViewHeight { + tableView?.isScrollEnabled = true + } + } + + if scrollView == self { return } + + + let changeValues = change as! [NSKeyValueChangeKey: AnyObject] - if diff > 0.0 { + if let new = changeValues[NSKeyValueChangeKey.newKey]?.cgPointValue, + let old = changeValues[NSKeyValueChangeKey.oldKey]?.cgPointValue { - handleScrollUp(scrollView!, - change: diff, - oldPosition: old) - } else { + let diff = old.y - new.y - handleScrollDown(scrollView!, - change: diff, - oldPosition: old) - } - } - } + if diff > 0.0 { + + handleScrollUp(scrollView!, + change: diff, + oldPosition: old) + } else { + + handleScrollDown(scrollView!, + change: diff, + oldPosition: old) + } + } + } func setContentOffset(_ scrollView: UIScrollView, point: CGPoint) { observing = false diff --git a/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift b/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift index 051d76d..d3964cd 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentedViewController.swift @@ -34,6 +34,8 @@ import UIKit - parameter index: index of selected segment. */ @objc optional func didMoveToPage(_ controller: UIViewController, segment: UIButton?, index: Int) + + @objc optional func didPullToRefress(_ refreshController: UIRefreshControl, selectedSegmet: UIViewController) } /** @@ -220,6 +222,8 @@ import UIKit } } + open var selectedSegmetViewController: UIViewController? + open var delegate:SJSegmentedViewControllerDelegate? var viewObservers = [UIView]() var segmentedScrollView = SJSegmentedScrollView(frame: CGRect.zero) @@ -300,11 +304,27 @@ import UIKit segmentedScrollView.segmentViewHeight = segmentViewHeight } + func pullToRefresh(_ refreshController: UIRefreshControl) { + if selectedSegmetViewController == nil { + self.selectedSegmetViewController = self.segmentControllers[0] + } + + for vc in (selectedSegmetViewController?.childViewControllers)! { + + } + + self.delegate?.didPullToRefress!(refreshController, selectedSegmet: selectedSegmetViewController!) + } + /** * Private method for adding the segmented scroll view. */ func addSegmentedScrollView() { + let refreshControl: UIRefreshControl = UIRefreshControl() + refreshControl.addTarget(self, action: #selector(pullToRefresh(_:)), for: .valueChanged) + segmentedScrollView.insertSubview(refreshControl, at: 0) + let topSpacing = SJUtil.getTopSpacing(self) segmentedScrollView.topSpacing = topSpacing @@ -340,6 +360,7 @@ import UIKit // selected segment at index segmentedScrollView.didSelectSegmentAtIndex = {(segment,index) in let selectedController = self.segmentControllers[index] + self.selectedSegmetViewController = selectedController self.delegate?.didMoveToPage?(selectedController, segment: segment!, index: index) @@ -413,12 +434,9 @@ import UIKit delegate?.didMoveToPage?(segmentControllers[0], segment: segment, index: 0) + } - - /** - * Method for atuomaticly scroll the segmet controller on top when button is taped - **/ - + open func animateToTop() { segmentedScrollView.scrollToTop(height: headerViewHeight) } From 484bd2ccb92131cf1e04074d9f0ae4830ec10dcb Mon Sep 17 00:00:00 2001 From: Ivan Yordanov Date: Tue, 18 Oct 2016 10:47:23 +0400 Subject: [PATCH 3/3] clear the mess --- .../Classes/SJSegmentView.swift | 357 ------------------ .../Classes/SJSegmentedScrollView.swift | 54 ++- 2 files changed, 26 insertions(+), 385 deletions(-) diff --git a/SJSegmentedScrollView/Classes/SJSegmentView.swift b/SJSegmentedScrollView/Classes/SJSegmentView.swift index c23ef36..6de3345 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentView.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentView.swift @@ -19,363 +19,6 @@ // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// SJSegmentView.swift -// SJSegmentedScrollView -// -// Created by Subins Jose on 10/06/16. -// Copyright © 2016 Subins Jose. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -// associated documentation files (the "Software"), to deal in the Software without restriction, -// including without limitation the rights to use, copy, modify, merge, publish, distribute, -// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import UIKit - -typealias DidSelectSegmentAtIndex = (_ segment:UIButton?, _ index: Int) -> Void - -class SJSegmentView: UIScrollView { - - var selectedSegmentViewColor: UIColor? { - didSet { - selectedSegmentView?.backgroundColor = selectedSegmentViewColor - } - } - - var titleColor: UIColor? { - didSet { - for segment in segments { - segment.setTitleColor(titleColor, for: UIControlState()) - } - } - } - - var segmentBackgroundColor: UIColor? { - didSet { - for segment in segments { - segment.backgroundColor = segmentBackgroundColor - } - } - } - - var shadow: SJShadow? { - didSet { - if let shadow = shadow { - layer.shadowOffset = shadow.offset - layer.shadowColor = shadow.color.cgColor - layer.shadowRadius = shadow.radius - layer.shadowOpacity = shadow.opacity - layer.masksToBounds = false; - } - } - } - - var font: UIFont? - var selectedSegmentViewHeight: CGFloat? - let kSegmentViewTagOffset = 100 - var segmentViewOffsetWidth: CGFloat = 10.0 - var titles: [String]? - var segments = [UIButton]() - var segmentContentView: UIView? - var didSelectSegmentAtIndex: DidSelectSegmentAtIndex? - var selectedSegmentView: UIView? - var xPosConstraints: NSLayoutConstraint? - var contentViewWidthConstraint: NSLayoutConstraint? - var selectedSegmentViewWidthConstraint: NSLayoutConstraint? - var contentSubViewWidthConstraints = [NSLayoutConstraint]() - - var contentView: SJContentView? { - didSet { - contentView!.addObserver(self, - forKeyPath: "contentOffset", - options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old], - context: nil) - } - } - - convenience init(frame: CGRect, segmentTitles: [String]) { - self.init(frame: frame) - - titles = segmentTitles - showsHorizontalScrollIndicator = false - showsVerticalScrollIndicator = false - bounces = false - - - NotificationCenter.default.addObserver(self, - selector: #selector(SJSegmentView.didChangeSegmentIndex(_:)), - name: NSNotification.Name("DidChangeSegmentIndex"), - object: nil) - } - - required override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - contentView!.removeObserver(self, - forKeyPath: "contentOffset", - context: nil) - - NotificationCenter.default.removeObserver(self, - name:NSNotification.Name("DidChangeSegmentIndex"), - object: nil) - } - - func didChangeSegmentIndex(_ notification: Notification) { - - //deselect previous buttons - for button in segments { - button.isSelected = false - } - - // select current button - let index = notification.object as? Int - let button = segments[index!] - button.isSelected = true - - } - - - func setSegmentsView(_ frame: CGRect) { - - let segmentWidth = getSegmentWidth(titles!, frame: frame) - createSegmentContentView(titles!, titleWidth: segmentWidth) - - var index = 0 - for title in titles! { - - createSegmentFor(title, width: segmentWidth, index: index) - index += 1 - } - - createSelectedSegmentView(segmentWidth) - - //Set first button as selected - let button = segments.first! as UIButton - button.isSelected = true - } - - func createSegmentContentView(_ titles: [String], titleWidth: CGFloat) { - - segmentContentView = UIView(frame: CGRect.zero) - segmentContentView!.translatesAutoresizingMaskIntoConstraints = false - addSubview(segmentContentView!) - - let contentViewWidth = titleWidth * CGFloat(titles.count) - let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[contentView]|", - options: [], - metrics: nil, - views: ["contentView": segmentContentView!, - "mainView": self]) - addConstraints(horizontalConstraints) - - contentViewWidthConstraint = NSLayoutConstraint(item: segmentContentView!, - attribute: .width, - relatedBy: .equal, - toItem: nil, - attribute: .notAnAttribute, - multiplier: 1.0, - constant: contentViewWidth) - addConstraint(contentViewWidthConstraint!) - - - let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[contentView(==mainView)]|", - options: [], - metrics: nil, - views: ["contentView": segmentContentView!, - "mainView": self]) - addConstraints(verticalConstraints) - } - - func createSegmentFor(_ title: String, width: CGFloat, index: Int) { - - let segmentView = getSegmentViewForController(title) - segmentView.tag = (index + kSegmentViewTagOffset) - segmentView.translatesAutoresizingMaskIntoConstraints = false - segmentContentView!.addSubview(segmentView) - - if segments.count == 0 { - - let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]", - options: [], - metrics: nil, - views: ["view": segmentView]) - segmentContentView!.addConstraints(horizontalConstraints) - - } else { - - let previousView = segments.last - let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[previousView]-0-[view]", - options: [], - metrics: nil, - views: ["view": segmentView, - "previousView": previousView!]) - segmentContentView!.addConstraints(horizontalConstraints) - } - - let widthConstraint = NSLayoutConstraint(item: segmentView, - attribute: .width, - relatedBy: .equal, - toItem: nil, - attribute: .notAnAttribute, - multiplier: 1.0, - constant: width) - segmentContentView!.addConstraint(widthConstraint) - contentSubViewWidthConstraints.append(widthConstraint) - - let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|", - options: [], - metrics: nil, - views: ["view": segmentView]) - segmentContentView!.addConstraints(verticalConstraints) - - segments.append(segmentView) - } - - func createSelectedSegmentView(_ width: CGFloat) { - - let segmentView = UIView() - segmentView.backgroundColor = selectedSegmentViewColor - segmentView.translatesAutoresizingMaskIntoConstraints = false - segmentContentView!.addSubview(segmentView) - selectedSegmentView = segmentView - - xPosConstraints = NSLayoutConstraint(item: segmentView, - attribute: .leading, - relatedBy: .equal, - toItem: segmentContentView!, - attribute: .leading, - multiplier: 1.0, - constant: 0.0) - segmentContentView!.addConstraint(xPosConstraints!) - - let segment = segments.first - let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[view(==segment)]", - options: [], - metrics: nil, - views: ["view": segmentView, - "segment": segment!]) - segmentContentView!.addConstraints(horizontalConstraints) - - let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[view(height)]|", - options: [], - metrics: ["height": selectedSegmentViewHeight!], - views: ["view": segmentView]) - segmentContentView!.addConstraints(verticalConstraints) - } - - func getSegmentViewForController(_ title: String) -> UIButton { - - let button = UIButton(type: .custom) - button.backgroundColor = segmentBackgroundColor - button.setTitleColor(titleColor, for: UIControlState()) - button.setTitle(title, for: UIControlState()) - button.titleLabel?.font = font - button.addTarget(self, action: #selector(SJSegmentView.onSegmentButtonPress(_:)), - for: .touchUpInside) - return button - } - - func onSegmentButtonPress(_ sender: AnyObject) { - - let index = sender.tag - kSegmentViewTagOffset - NotificationCenter.default.post(name: Notification.Name(rawValue: "DidChangeSegmentIndex"), - object: index) - - if didSelectSegmentAtIndex != nil { - didSelectSegmentAtIndex!(sender as? UIButton, index) - } - } - - func getSegmentWidth(_ titles: [String], frame: CGRect) -> CGFloat { - - var maxWidth: CGFloat = 0 - - // find max width of segement - for title in titles { - - let string: NSString = title as NSString - let width = string.size(attributes: [NSFontAttributeName: font!]).width - - if width > maxWidth { - maxWidth = width - } - } - - let width = Int(maxWidth + segmentViewOffsetWidth) - let totalWidth = width * titles.count - if totalWidth < Int(frame.size.width) { - maxWidth = frame.size.width / CGFloat(titles.count) - } else { - maxWidth = CGFloat(width) - } - - return maxWidth - } - - override func observeValue(forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey : Any]?, - context: UnsafeMutableRawPointer?) { - - if let change = change as [NSKeyValueChangeKey : AnyObject]? { - if let old = change[NSKeyValueChangeKey.oldKey], let new = change[NSKeyValueChangeKey.newKey] { - if !(old.isEqual(new)) { - //update selected segment view x position - let scrollView = object as? UIScrollView - var changeOffset = (scrollView?.contentSize.width)! / contentSize.width - let value = (scrollView?.contentOffset.x)! / changeOffset - - if !value.isNaN { - selectedSegmentView?.frame.origin.x = (scrollView?.contentOffset.x)! / changeOffset - } - - //update segment offset x position - let segmentScrollWidth = contentSize.width - bounds.width - let contentScrollWidth = scrollView!.contentSize.width - scrollView!.bounds.width - changeOffset = segmentScrollWidth / contentScrollWidth - contentOffset.x = (scrollView?.contentOffset.x)! * changeOffset - } - } - } - } - - func didChangeParentViewFrame(_ frame: CGRect) { - - let segmentWidth = getSegmentWidth(titles!, frame: frame) - let contentViewWidth = segmentWidth * CGFloat(titles!.count) - contentViewWidthConstraint?.constant = contentViewWidth - - for constraint in contentSubViewWidthConstraints { - constraint.constant = segmentWidth - } - - let changeOffset = (contentView?.contentSize.width)! / contentSize.width - let value = (contentView?.contentOffset.x)! / changeOffset - - if !value.isNaN { - xPosConstraints!.constant = (selectedSegmentView?.frame.origin.x)! - layoutIfNeeded() - } - } -} - import UIKit diff --git a/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift b/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift index 3786535..76ceb62 100644 --- a/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift +++ b/SJSegmentedScrollView/Classes/SJSegmentedScrollView.swift @@ -308,15 +308,14 @@ class SJSegmentedScrollView: UIScrollView { } } - override func observeValue(forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey : Any]?, - context: UnsafeMutableRawPointer?) { - - if !observing { return } + override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + if !observing { return } - let scrollView = object as? UIScrollView - if scrollView == nil { return } + let scrollView = object as? UIScrollView + if scrollView == nil { return } if (scrollView?.isKind(of: UIScrollView.classForCoder()))! { if scrollView!.contentOffset.y >= headerViewHeight { @@ -324,29 +323,28 @@ class SJSegmentedScrollView: UIScrollView { } } - if scrollView == self { return } - - - let changeValues = change as! [NSKeyValueChangeKey: AnyObject] + if scrollView == self { return } - if let new = changeValues[NSKeyValueChangeKey.newKey]?.cgPointValue, - let old = changeValues[NSKeyValueChangeKey.oldKey]?.cgPointValue { + let changeValues = change as! [NSKeyValueChangeKey: AnyObject] - let diff = old.y - new.y + if let new = changeValues[NSKeyValueChangeKey.newKey]?.cgPointValue, + let old = changeValues[NSKeyValueChangeKey.oldKey]?.cgPointValue { - if diff > 0.0 { - - handleScrollUp(scrollView!, - change: diff, - oldPosition: old) - } else { - - handleScrollDown(scrollView!, - change: diff, - oldPosition: old) - } - } - } + let diff = old.y - new.y + + if diff > 0.0 { + + handleScrollUp(scrollView!, + change: diff, + oldPosition: old) + } else { + + handleScrollDown(scrollView!, + change: diff, + oldPosition: old) + } + } + } func setContentOffset(_ scrollView: UIScrollView, point: CGPoint) { observing = false