代码之家  ›  专栏  ›  技术社区  ›  brandonscript

使用hitTest()或point(内部,带有事件)从添加到keyWindow的视图转发事件

  •  0
  • brandonscript  · 技术社区  · 7 年前

    我有一个小的、可重用的UIView小部件,它可以添加到任何地方的任何视图中,并且可以也可以不一定总是在同一个位置或具有相同的框架。看起来像这样:

    class WidgetView: UIView {
        // some stuff, not very exciting
    }
    

    class WidgetView: UIView {
        // some stuff, not very exciting
    
        var overlay: UIView!
    
        commonInit() {
            guard let keyWindow = UIApplication.shared.keyWindow else { return }
            overlay = UIView(frame: keyWindow.frame)
            overlay.alpha = 0
            keyWindow.addSubview(overlay)
    
            // Set some constraints here
    
            someControls = CustomControlsView( ... a smaller controls view ... ) 
            overlay.addSubview(someControls)
    
            // Set some more constraints here!
        }            
    
        showOverlay() {
            overlay.alpha = 1
        }
    
        hideOverlay() {
            overlay.alpha = 0
        }
    }
    

    问题变得复杂了,是我在切割原稿的形状 WidgetView 使其控件在下面仍然可见。这很好:

    class CutoutView: UIView {
    
        var holes: [CGRect]?
    
        convenience init(holes: [CGRect], backgroundColor: UIColor?) {
            self.init()
    
            self.holes = holes
    
            self.backgroundColor = backgroundColor ?? UIColor.black.withAlphaComponent(0.5)
            isOpaque = false
        }
    
        override func draw(_ rect: CGRect) {
            backgroundColor?.setFill()
            UIRectFill(rect)
    
            guard let rectsArray = holes else {
                return
            }
    
            for holeRect in rectsArray {
                let holeRectIntersection = rect.intersection(holeRect)
                UIColor.clear.setFill()
                UIRectFill(holeRectIntersection)
            }
        }
    }
    

    ... 除了 问题

    没有通过切口的接触。所以我想我应该聪明点 this hitTest() point(inside, with event) 不要对外界的接触做出反应 的框架。

    在我看来,有四种(潜在的)方法可以解决这个问题,但是我不能让它们中的任何一个起作用。

    1. 找到一些神奇的()方法来让击键测试或点(内部)在 keyWindow ,或者至少 overlayView

    2. 添加 UITapGestureRecognizer 并将适当的触碰转发到原始视图控制器(这部分起作用-轻触手势响应,但我不知道从那里到哪里)

    3. WidgetView公司 对触摸作出反应

    4. 将覆盖及其子视图添加到一个不同的父视图中,而不是keyWindow?


    source 'https://github.com/CocoaPods/Specs.git'
    platform :ios, '10.0'
    use_frameworks!
    
    target 'YourTarget' do
        pod 'SnapKit', '~> 4.2.0'
    end
    

    视图控制器.swift

    import UIKit
    import SnapKit
    
    class ViewController: UIViewController {
    
        public var utilityToolbar: UtilityToolbar!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.backgroundColor = .darkGray
            setup()
    
        }
    
        func setup() {
    
            let button1 = UtilityToolbar.Button(title: "One", buttonPressed: nil)
            let button2 = UtilityToolbar.Button(title: "Two", buttonPressed: nil)
            let button3 = UtilityToolbar.Button(title: "Three", buttonPressed: nil)
            let button4 = UtilityToolbar.Button(title: "Four", buttonPressed: nil)
            let button5 = UtilityToolbar.Button(title: "Five", buttonPressed: nil)
    
            let menuItems: [UtilityToolbar.Button] = [button1, button2, button3, button4, button5]
            menuItems.forEach({
                $0.setTitleColor(#colorLiteral(red: 0.1963312924, green: 0.2092989385, blue: 0.2291107476, alpha: 1), for: .normal)
            })
    
            utilityToolbar = UtilityToolbar(title: "One", menuItems: menuItems)
            utilityToolbar.titleButton.setTitleColor(#colorLiteral(red: 0.1963312924, green: 0.2092989385, blue: 0.2291107476, alpha: 1), for: .normal)
            utilityToolbar.backgroundColor = .white
            utilityToolbar.dropdownContainer.backgroundColor = .white
    
            view.addSubview(utilityToolbar)
    
            utilityToolbar.snp.makeConstraints { (make) in
                make.left.right.equalToSuperview()
                make.top.equalToSuperview().offset(250)
                make.height.equalTo(50.0)
            }
        }
    }
    

    剪贴画.swift

    import UIKit
    
    class CutoutView: UIView {
    
        var holes: [CGRect]?
    
        convenience init(holes: [CGRect], backgroundColor: UIColor?) {
            self.init()
            self.holes = holes
            self.backgroundColor = backgroundColor ?? UIColor.black.withAlphaComponent(0.5)
            isOpaque = false
        }
    
        override func draw(_ rect: CGRect) {
            backgroundColor?.setFill()
            UIRectFill(rect)
    
            guard let rectsArray = holes else { return }
    
            for holeRect in rectsArray {
                let holeRectIntersection = rect.intersection(holeRect)
                UIColor.clear.setFill()
                UIRectFill(holeRectIntersection)
            }
        }
    
    }
    

    实用工具工具栏.swift

    import Foundation import UIKit import SnapKit
    
    class UtilityToolbar: UIView {
    
        class Button: UIButton {
    
            var functionIdentifier: String?
            var buttonPressed: (() -> Void)?
    
            fileprivate var owner: UtilityToolbar?
    
            convenience init(title: String, buttonPressed: (() -> Void)?) {
                self.init(type: .custom)
                self.setTitle(title, for: .normal)
                self.functionIdentifier = title.lowercased()
                self.buttonPressed = buttonPressed
            }
        }
    
        enum MenuState {
            case open
            case closed
        }
    
        enum TitleStyle {
            case label
            case dropdown
        }
    
        private(set) public var menuState: MenuState = .closed
    
        var itemHeight: CGFloat = 50.0
        var spacing: CGFloat = 6.0 { didSet { dropdownStackView.spacing = spacing } }
        var duration: TimeInterval = 0.15
        var dropdownContainer: UIView!
        var titleButton: UIButton = UIButton()
    
        @IBOutlet weak fileprivate var toolbarStackView: UIStackView!
        private var stackViewBottomConstraint: Constraint!
        private var dropdownStackView: UIStackView!
        private var overlayView: CutoutView!
        private var menuItems: [Button] = []
        private var expandedHeight: CGFloat { get { return CGFloat(menuItems.count - 1) * itemHeight + (spacing * 2) } }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            commonInit()
        }
    
        convenience init(title: String, menuItems: [Button]) {
            self.init()
            self.titleButton.setTitle(title, for: .normal)
            self.menuItems = menuItems
            commonInit()
        }
    
        private func commonInit() {
    
            self.addSubview(titleButton)
            titleButton.addTarget(self, action: #selector(titleButtonPressed(_:)), for: .touchUpInside)
            titleButton.snp.makeConstraints { $0.edges.equalToSuperview() }
    
            dropdownContainer = UIView()
    
            dropdownStackView = UIStackView()
            dropdownStackView.axis = .vertical
            dropdownStackView.distribution = .fillEqually
            dropdownStackView.alignment = .fill
            dropdownStackView.spacing = spacing
            dropdownStackView.alpha = 0
            dropdownStackView.translatesAutoresizingMaskIntoConstraints = true
    
            menuItems.forEach({
                $0.owner = self
                $0.addTarget(self, action: #selector(menuButtonPressed(_:)), for: .touchUpInside)
            })
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            // Block if the view isn't fully ready, or if the containerView has already been added to the window
            guard
                let keyWindow = UIApplication.shared.keyWindow,
                self.globalFrame != .zero,
                dropdownContainer.superview == nil else { return }
    
            overlayView = CutoutView(frame: keyWindow.frame)
            overlayView.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5)
            overlayView.alpha = 0
            overlayView.holes = [self.globalFrame!]
            keyWindow.addSubview(overlayView)
            keyWindow.addSubview(dropdownContainer)
            dropdownContainer.snp.makeConstraints { (make) in
                make.left.right.equalToSuperview()
                make.top.equalToSuperview().offset((self.globalFrame?.origin.y ?? 0) + self.frame.height)
                make.height.equalTo(0)
            }
    
            dropdownContainer.addSubview(dropdownStackView)
    
            dropdownStackView.snp.makeConstraints({ (make) in
                make.left.right.equalToSuperview().inset(spacing).priority(.required)
                make.top.equalToSuperview().priority(.medium)
                stackViewBottomConstraint = make.bottom.equalToSuperview().priority(.medium).constraint
            })
        }
    
        public func openMenu() {
    
            titleButton.isSelected = true
            dropdownStackView.addArrangedSubviews(menuItems.filter { $0.titleLabel?.text != titleButton.titleLabel?.text })
            dropdownContainer.layoutIfNeeded()
            dropdownContainer.snp.updateConstraints { (make) in
                make.height.equalTo(self.expandedHeight)
            }
    
            stackViewBottomConstraint.update(inset: spacing)
    
            UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
                self.overlayView.alpha = 1
                self.dropdownStackView.alpha = 1
                self.dropdownContainer.superview?.layoutIfNeeded()
            }) { (done) in
                self.menuState = .open
            }
    
        }
    
        public func closeMenu() {
    
            titleButton.isSelected = false
            dropdownContainer.snp.updateConstraints { (make) in
                make.height.equalTo(0)
            }
            stackViewBottomConstraint.update(inset: 0)
    
            UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
                self.overlayView.alpha = 0
                self.dropdownStackView.alpha = 0
                self.dropdownContainer.superview?.layoutIfNeeded()
            }) { (done) in
                self.menuState = .closed
                self.dropdownStackView.removeAllArrangedSubviews()
            }
        }
    
        @objc private func titleButtonPressed(_ sender: Button) {
            switch menuState {
            case .open:
                closeMenu()
            case .closed:
                openMenu()
            }
        }
    
        @objc private func menuButtonPressed(_ sender: Button) {
            closeMenu()
        }
    
        override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            // Nothing of interest is happening here unless the touch is inside the containerView
            print(UIColor.colorOfPoint(point: point, in: overlayView).cgColor.alpha > 0)
            if UIColor.colorOfPoint(point: point, in: overlayView).cgColor.alpha > 0 {
                return true
            }
            return super.point(inside: point, with: event)
        } }
    

    扩展名.swift

    import UIKit
    
    extension UIWindow {
        static var topController: UIViewController? {
            get {
                guard var topController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
                while let presentedViewController = topController.presentedViewController {
                    topController = presentedViewController
                }
                return topController
            }
        }
    }
    
    public extension UIView {
        var globalPoint: CGPoint? {
            return self.superview?.convert(self.frame.origin, to: nil)
        }
    
        var globalFrame: CGRect? {
            return self.superview?.convert(self.frame, to: nil)
        }
    }
    
    extension UIColor {
        static func colorOfPoint(point:CGPoint, in view: UIView) -> UIColor {
    
            var pixel: [CUnsignedChar] = [0, 0, 0, 0]
    
            let colorSpace = CGColorSpaceCreateDeviceRGB()
            let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
    
            let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
    
            context!.translateBy(x: -point.x, y: -point.y)
    
            view.layer.render(in: context!)
    
            let red: CGFloat   = CGFloat(pixel[0]) / 255.0
            let green: CGFloat = CGFloat(pixel[1]) / 255.0
            let blue: CGFloat  = CGFloat(pixel[2]) / 255.0
            let alpha: CGFloat = CGFloat(pixel[3]) / 255.0
    
            let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)
    
            return color
        }
    }
    
    extension UIStackView {
        func addArrangedSubviews(_ views: [UIView?]) {
            views.filter({$0 != nil}).forEach({ self.addArrangedSubview($0!)})
        }
    
        func removeAllArrangedSubviews() {
    
            let removedSubviews = arrangedSubviews.reduce([]) { (allSubviews, subview) -> [UIView] in
                self.removeArrangedSubview(subview)
                return allSubviews + [subview]
            }
    
            // Deactivate all constraints
            NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints }))
    
            // Remove the views from self
            removedSubviews.forEach({ $0.removeFromSuperview() })
        }
    }
    
    0 回复  |  直到 7 年前
        1
  •  1
  •   brandonscript    7 年前

    我傻了,我要把 hitTest 在覆盖视图上( CutoutView

    class CutoutView: UIView {
    
        // ...
    
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            guard UIColor.colorOfPoint(point: point, in: self).cgColor.alpha > 0 else { return nil }
            return super.hitTest(point, with: event)
        }
    }
    
    推荐文章