Skip to content

Instantly share code, notes, and snippets.

@sora0077
Last active June 10, 2017 02:05
Show Gist options
  • Save sora0077/cdd88c2369e8e79f89e46027aaeb8f52 to your computer and use it in GitHub Desktop.
Save sora0077/cdd88c2369e8e79f89e46027aaeb8f52 to your computer and use it in GitHub Desktop.
HardRubberbandView.swift
import UIKit
final class HardRubberbandView: UIView, UIScrollViewDelegate {
var contentSize: CGSize = .zero {
didSet {
scrollView.contentSize = contentSize
contentView.frame.size = contentSize
}
}
@objc
private(set) var contentOffset: CGPoint = .zero {
willSet { willChangeValue(forKey: #keyPath(HardRubberbandView.contentOffset)) }
didSet { didChangeValue(forKey: #keyPath(HardRubberbandView.contentOffset)) }
}
/// range 0 ~ 1
var hardness: CGFloat = 0.84 {
didSet { assert((0...1).contains(hardness), "`hardness` can only set range 0 ~ 1") }
}
let contentView = UIView()
private let scrollView = UIScrollView()
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
scrollView.frame = bounds
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.delegate = self
addSubview(scrollView)
scrollView.addSubview(contentView)
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame.size = scrollView.contentSize
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateOffset(with: scrollView.contentOffset)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
updateOffset(with: scrollView.contentOffset)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updateOffset(with: scrollView.contentOffset)
}
private func updateOffset(with newOffset: CGPoint) {
let origin: CGPoint = {
var p: CGPoint = .zero
if newOffset.x < 0 {
p.x = newOffset.x * hardness
} else if (newOffset.x + scrollView.bounds.width) > scrollView.contentSize.width {
p.x = (newOffset.x + scrollView.bounds.width - scrollView.contentSize.width) * hardness
}
if newOffset.y < 0 {
p.y = newOffset.y * hardness
} else if (newOffset.y + scrollView.bounds.height) > scrollView.contentSize.height {
p.y = (newOffset.y + scrollView.bounds.height - scrollView.contentSize.height) * hardness
}
return p
}()
contentView.frame.origin = origin
contentOffset = {
var p = contentView.convert(CGPoint.zero, to: self)
p.x = -p.x
p.y = -p.y
return p
}()
}
}
final class ViewController: UIViewController {
private let scrollView = HardRubberbandView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(scrollView)
scrollView.frame = CGRect(x: 0, y: 300, width: view.bounds.width, height: 100)
scrollView.contentSize = CGSize(width: 1000, height: 100)
scrollView.contentView.backgroundColor = .orange
for color in [UIColor.blue, .red, .brown, .yellow] {
let superview = scrollView.contentView
let view = UIView()
view.backgroundColor = color
let prev = superview.subviews.last
superview.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.leftAnchor.constraint(equalTo: prev?.rightAnchor ?? superview.leftAnchor, constant: 10).isActive = true
view.heightAnchor.constraint(equalToConstant: 80).isActive = true
view.topAnchor.constraint(equalTo: superview.topAnchor, constant: 10).isActive = true
if let prev = prev {
view.widthAnchor.constraint(equalTo: prev.widthAnchor, multiplier: 1).isActive = true
}
}
scrollView.contentView.subviews.last?.rightAnchor.constraint(equalTo: scrollView.contentView.rightAnchor, constant: -10).isActive = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment