swiftuikitmousehovermac-catalystsegmentedcontrol

Getting trouble when hovering a custom segmented control in swift


I tried to create custom segmented control which should change its look if you are hovering it. But it does not work as I expected.

Here's the code (I am sorry that it is that long but I already made it 200 lines shorter then it was):

class MySegmentedControl: UIView {
    var state = MySegmentedControl_State.unknown
    var buttons = [MySegmentedControl_ButtonView]()
    func setUpView(){
        let view = UIView(frame: self.frame)
        view.frame.origin.x = 0
        view.frame.origin.y = 0
        view.addSubview(holderView())
        self.addSubview(view)
    }
    func holderView() -> UIView{
        let view = UIView(frame: CGRect(origin: CGPoint(x: 100, y: 0), size: CGSize(width: 490, height: 70)))
        view.layer.borderWidth = 5
        view.layer.borderColor = UIColor.gray.cgColor
        view.layer.cornerRadius = view.frame.height / 2
        view.layer.masksToBounds = true
        view.clipsToBounds = true
        view.backgroundColor = #colorLiteral(red: 0.5741485357, green: 0.5741624236, blue: 0.574154973, alpha: 1)
        //place the first button
        let button_1 = MySegmentedControl_ButtonView(text: "One", frame: CGRect(x: 0, y: 0, width: 163.3333, height: 70), type: .one, delegate: self)
        //place the second Button
        let button_2 = MySegmentedControl_ButtonView(text: "Two", frame: CGRect(x: 163.3333, y: 0, width: 163.3333, height: 70), type: .two, delegate: self)
        //place the third Button
        let button_3 = MySegmentedControl_ButtonView(text: "Three", frame: CGRect(x: 163.3333*2, y: 0, width: 163.3333, height: 70), type: .three, delegate: self)
        buttons.append(button_1); buttons.append(button_2); buttons.append(button_3)
        view.addSubview(button_1)
        view.addSubview(button_2)
        view.addSubview(button_3)
        
        return view
    }
    
    
    class MySegmentedControl_ButtonView: UIView{
        private var selected = false
        private var hovering = false
        var text = ""
        private var type: MySegmentedControl_State
        private var delegate: MySegmentedControl_ButtonView_Delegate
        private var label = UILabel()
        func setUpView(){
            layer.cornerRadius = frame.height/2
            let label = UILabel(frame: frame)
            self.label = label
            label.tintColor = .white
            label.text = text
            label.sizeToFit()
            label.center = self.center
            label.font =  label.font.withSize(15)
            let regionizer = UIHoverGestureRecognizer(target: self, action: #selector(didHover(_:)))
            addGestureRecognizer(regionizer)
            addSubview(label)
            //set up the button
            let button = UIButton(frame: bounds, primaryAction: UIAction(handler: { [self] action in
                selected = true
                delegate.shouldChangeState(to: type)
                delegate.shouldDeselectOthers(without: text)
                if !hovering{
                    backgroundColor = #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.8)
                }else{
                    self.backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.9435433103)
                }
                
            }))
            addSubview(button)
        }
        
        func deselect(){
            self.selected = false
            if hovering{
                backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15)
                UIView.animate(withDuration: 0.2) {[self]in
                    label.frame.origin.y = 10
                    label.font =  label.font.withSize(12)
                    
                }
            }else{
                backgroundColor = #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15)
                UIView.animate(withDuration: 0.2) {[self]in
                    label.frame.origin.y = 10
                    label.font =  label.font.withSize(12)
                    
                }
            }
        }
        
        @objc
        func didHover(_ recognizer: UIHoverGestureRecognizer){
            switch recognizer.state{
            case .began, .changed:
                hovering = true
                if selected{
                    backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15)
                    UIView.animate(withDuration: 0.2) {[self]in
                        label.frame.origin.y = 10
                        label.font =  label.font.withSize(12)
                        
                    }
                }else{
                    backgroundColor = #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15)
                    UIView.animate(withDuration: 0.2) {[self]in
                        label.frame.origin.y = 10
                        label.font =  label.font.withSize(12)
                        
                    }
                }
                
            case .ended:
                hovering = false
                if selected{
                    self.backgroundColor = #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.2)
                    UIView.animate(withDuration: 0.2) {[self]in
                        label.center.y = center.y
                        label.font =  label.font.withSize(15)
                        
                    }
                }else{
                    self.backgroundColor = .clear
                    UIView.animate(withDuration: 0.2) {[self]in
                        label.center.y = center.y
                        label.font =  label.font.withSize(15)
                        
                    }
                }
            default:break
            }
        }
        
       
        
        init(text: String, frame: CGRect, type: MySegmentedControl_State, delegate: MySegmentedControl_ButtonView_Delegate){
            
            self.type = type
            self.delegate = delegate
            super.init(frame: frame)
            self.text = text
            setUpView()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
}

extension MySegmentedControl: MySegmentedControl_ButtonView_Delegate{
    func shouldDeselectOthers(without: String) {
        for button in buttons{
            if button.text != without{
                button.deselect()
            }
        }
    }
    
    func shouldChangeState(to state: MySegmentedControl_State) {
        self.state = state
    }
    
    
}

protocol MySegmentedControl_ButtonView_Delegate{
    func shouldDeselectOthers(without: String)
    func shouldChangeState(to state: MySegmentedControl_State)
}


enum MySegmentedControl_State: String{
    case unknown = "Non specific case avalaible"
    case one = "One selected"
    case two = "Two selected"
    case three = "Three selected"
}

But the second of my buttons is displayed as the third and the the third does not get displayed, but if I hover the place where button two SHOULD be, it still gets hovered.

Here is a shout video:

Video showing problems with this code

My App is running on MacOS with MacCatalyst


Solution

  • In setUpView() inside class MySegmentedControl_ButtonView, you are trying to set the label's position with:

    label.center = self.center
    

    However, self.center is relative to self's superview. So your labels are being shifted.

    If you change that line to:

    label.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
    

    the label centering will be correct.

    As I asked in my comment though... why aren't you using auto-layout? It would make things much easier (and much more flexible).