summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/AccountInputGroupView.swift
blob: e65443933f24e9c4fda1f8355dff05d7afbfe428 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//
//  AccountInputGroupView.swift
//  MullvadVPN
//
//  Created by pronebird on 22/03/2019.
//  Copyright © 2019 Amagicom AB. All rights reserved.
//

import UIKit

@IBDesignable class AccountInputGroupView: UIView {
    
    @IBOutlet var textField: UITextField!
    
    private let borderRadius = CGFloat(8)
    private let borderWidth = CGFloat(2)
    
    private let borderLayer = CAShapeLayer()
    private let backgroundLayer = CAShapeLayer()
    private let maskLayer = CALayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    // MARK: - CALayerDelegate
    
    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
        
        guard layer == self.layer else { return }
        
        // extend the border frame outside of the content area
        let borderFrame = layer.bounds.insetBy(dx: -borderWidth * 0.5, dy: -borderWidth * 0.5)
        
        // create a bezier path for border
        let borderPath = borderBezierPath(size: borderFrame.size)
        
        // update the background layer mask
        maskLayer.frame.size = borderFrame.size
        maskLayer.contents = backgroundMaskImage(borderPath: borderPath).cgImage
        
        backgroundLayer.frame = borderFrame
        
        borderLayer.path = borderPath.cgPath
        borderLayer.frame = borderFrame
    }
    
    // MARK: - Notifications
    
    @objc func textDidBeginEditing() {
        updateBorderStyle()
    }
    
    @objc func textDidEndEditing() {
        updateBorderStyle()
    }
    
    // MARK: - Private
    
    private func setup() {
        backgroundColor = UIColor.clear
        
        borderLayer.lineWidth = borderWidth
        borderLayer.strokeColor = UIColor.clear.cgColor
        borderLayer.fillColor = UIColor.clear.cgColor
        
        backgroundLayer.backgroundColor = UIColor.white.cgColor
        backgroundLayer.mask = maskLayer
        
        layer.insertSublayer(borderLayer, at: 0)
        layer.insertSublayer(backgroundLayer, at: 0)
        
        addTextFieldNotificationObservers()
    }
    
    
    private func addTextFieldNotificationObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(textDidBeginEditing), name: UITextField.textDidBeginEditingNotification, object: textField)
        NotificationCenter.default.addObserver(self, selector: #selector(textDidEndEditing), name: UITextField.textDidEndEditingNotification, object: textField)
    }
    
    private func updateBorderStyle() {
        let borderColor = textField.isEditing ? UIColor.accountTextFieldBorderColor : UIColor.clear
        
        borderLayer.strokeColor = borderColor.cgColor
    }
    
    private func borderBezierPath(size: CGSize) -> UIBezierPath {
        let borderPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: borderRadius)
        borderPath.lineWidth = borderWidth
        
        return borderPath
    }
    
    private func backgroundMaskImage(borderPath: UIBezierPath) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: borderPath.bounds)
        return renderer.image { (ctx) in
            borderPath.fill()
            
            // strip out any overlapping pixels between the border and the background
            borderPath.stroke(with: .clear, alpha: 0)
        }
    }
}