In iOS, how do I create a button that is always on top of all other view controllers?

前端 未结 8 1888
悲哀的现实
悲哀的现实 2020-11-28 02:09

No matter if modals are presented or the user performs any type of segue.

Is there a way to keep the button \"always on top\" (not the top of the screen) throughout

8条回答
  •  误落风尘
    2020-11-28 02:39

    In Swift 3:

    import UIKit
    
    private class FloatingButtonWindow: UIWindow {
        var button: UIButton?
    
        var floatingButtonController: FloatingButtonController?
    
        init() {
            super.init(frame: UIScreen.main.bounds)
            backgroundColor = nil
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        fileprivate override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            guard let button = button else { return false }
            let buttonPoint = convert(point, to: button)
            return button.point(inside: buttonPoint, with: event)
        }
    }
    
    class FloatingButtonController: UIViewController {
        private(set) var button: UIButton!
    
        private let window = FloatingButtonWindow()
    
        required init?(coder aDecoder: NSCoder) {
            fatalError()
        }
    
        init() {
            super.init(nibName: nil, bundle: nil)
            window.windowLevel = CGFloat.greatestFiniteMagnitude
            window.isHidden = false
            window.rootViewController = self
            NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
        }
    
        func keyboardDidShow(note: NSNotification) {
            window.windowLevel = 0
            window.windowLevel = CGFloat.greatestFiniteMagnitude
        }
    
        override func loadView() {
            let view = UIView()
            let button = UIButton(type: .custom)
            button.setTitle("Floating", for: .normal)
            button.setTitleColor(UIColor.green, for: .normal)
            button.backgroundColor = UIColor.white
            button.layer.shadowColor = UIColor.black.cgColor
            button.layer.shadowRadius = 3
            button.layer.shadowOpacity = 0.8
            button.layer.shadowOffset = CGSize.zero
            button.sizeToFit()
            button.frame = CGRect(origin: CGPoint(x: 10, y: 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: #selector(panDidFire))
            button.addGestureRecognizer(panner)
        }
    
        func panDidFire(panner: UIPanGestureRecognizer) {
            let offset = panner.translation(in: view)
            panner.setTranslation(CGPoint.zero, in: view)
            var center = button.center
            center.x += offset.x
            center.y += offset.y
            button.center = center
    
            if panner.state == .ended || panner.state == .cancelled {
                UIView.animate(withDuration: 0.3) {
                    self.snapButtonToSocket()
                }
            }
        }
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            snapButtonToSocket()
        }
    
        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] = [
                CGPoint(x: rect.minX, y: rect.minY),
                CGPoint(x: rect.minX, y: rect.maxY),
                CGPoint(x: rect.maxX, y: rect.minY),
                CGPoint(x: rect.maxX, y: rect.maxY),
                CGPoint(x: rect.midX, y: rect.midY)
            ]
            return sockets
        }
    
        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
        }
    }
    

    And in AppDelegate:

        var floatingButtonController: FloatingButtonController?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
    
         floatingButtonController = FloatingButtonController()
         floatingButtonController?.button.addTarget(self, action: #selector(AppDelegate.floatingButtonWasTapped), for: .touchUpInside)
    
    
        return true
    }
    
    func floatingButtonWasTapped() {
        let alert = UIAlertController(title: "Warning", message: "Don't do that!", preferredStyle: .alert)
        let action = UIAlertAction(title: "Sorry…", style: .default, handler: nil)
        alert.addAction(action)
        window?.rootViewController?.present(alert, animated: true, completion: nil)
    }
    

提交回复
热议问题