Last active
February 27, 2024 11:21
-
-
Save mayoff/ea37ee75a87efab5d7e8 to your computer and use it in GitHub Desktop.
A floating button on iOS for http://stackoverflow.com/q/34777558/77567
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class FloatingButtonController: UIViewController { | |
private(set) var button: UIButton! | |
required init?(coder aDecoder: NSCoder) { | |
fatalError() | |
} | |
init() { | |
super.init(nibName: nil, bundle: nil) | |
window.windowLevel = CGFloat.max | |
window.hidden = false | |
window.rootViewController = self | |
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil) | |
} | |
private let window = FloatingButtonWindow() | |
override func loadView() { | |
let view = UIView() | |
let button = UIButton(type: .Custom) | |
button.setTitle("Floating", forState: .Normal) | |
button.setTitleColor(UIColor.greenColor(), forState: .Normal) | |
button.backgroundColor = UIColor.whiteColor() | |
button.layer.shadowColor = UIColor.blackColor().CGColor | |
button.layer.shadowRadius = 3 | |
button.layer.shadowOpacity = 0.8 | |
button.layer.shadowOffset = CGSize.zero | |
button.sizeToFit() | |
button.frame = CGRect(origin: CGPointMake(10, 10), size: button.bounds.size) | |
button.autoresizingMask = [] | |
view.addSubview(button) | |
self.view = view | |
self.button = button | |
window.button = button | |
let panner = UIPanGestureRecognizer(target: self, action: "panDidFire:") | |
button.addGestureRecognizer(panner) | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
snapButtonToSocket() | |
} | |
func panDidFire(panner: UIPanGestureRecognizer) { | |
let offset = panner.translationInView(view) | |
panner.setTranslation(CGPoint.zero, inView: view) | |
var center = button.center | |
center.x += offset.x | |
center.y += offset.y | |
button.center = center | |
if panner.state == .Ended || panner.state == .Cancelled { | |
UIView.animateWithDuration(0.3) { | |
self.snapButtonToSocket() | |
} | |
} | |
} | |
func keyboardDidShow(note: NSNotification) { | |
window.windowLevel = 0 | |
window.windowLevel = CGFloat.max | |
} | |
private func snapButtonToSocket() { | |
var bestSocket = CGPoint.zero | |
var distanceToBestSocket = CGFloat.infinity | |
let center = button.center | |
for socket in sockets { | |
let distance = hypot(center.x - socket.x, center.y - socket.y) | |
if distance < distanceToBestSocket { | |
distanceToBestSocket = distance | |
bestSocket = socket | |
} | |
} | |
button.center = bestSocket | |
} | |
private var sockets: [CGPoint] { | |
let buttonSize = button.bounds.size | |
let rect = view.bounds.insetBy(dx: 4 + buttonSize.width / 2, dy: 4 + buttonSize.height / 2) | |
let sockets: [CGPoint] = [ | |
CGPointMake(rect.minX, rect.minY), | |
CGPointMake(rect.minX, rect.maxY), | |
CGPointMake(rect.maxX, rect.minY), | |
CGPointMake(rect.maxX, rect.maxY), | |
CGPointMake(rect.midX, rect.midY) | |
] | |
return sockets | |
} | |
} | |
private class FloatingButtonWindow: UIWindow { | |
var button: UIButton? | |
init() { | |
super.init(frame: UIScreen.mainScreen().bounds) | |
backgroundColor = nil | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
private override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { | |
guard let button = button else { return false } | |
let buttonPoint = convertPoint(point, toView: button) | |
return button.pointInside(buttonPoint, withEvent: event) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
in SceneDelegate: