Neumorphismの電卓を作ってみる


# Neumorphismの電卓

Neumorphismのデザインが素敵だったので，練習で仲間と一緒に電卓を作ってみました！
とりあえず動くようになったので公開しようと思います。
ほとんど彼につくってもらいましたが...


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352824/cb5363d9-b166-4b3b-436f-cddc69593c73.png)

この電卓は，主に```NeumorLabel```と```NeumorButton```の２つから作られています。

# Neumorphismとは
参考
>https://note.com/hironobukimura/n/n0431c73714e8

ボタンなどの要素が凹凸で表現されている，新しいスキューモーフィックデザインのことみたいです。

# NeumorLabel
## コード全体
```swift/NeumorLabel.swift
import Foundation
import UIKit

class NeumorLabel: UILabel {
    override init(frame: CGRect) {
        super.init(frame: frame)
        label()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        label()
    }
    
    private let params = Params()
    
    private let topAndBottomLayer = CAGradientLayer(),
                leftAndRightLayer = CAGradientLayer(),
                calcResultLabel = UILabel()
    
    private func label() {
        putTopAndBottom()
        putLeftAndRight()
        addSubview(calcResultLabel)
    }
    private func putTopAndBottom() {
        topAndBottomLayer.cornerRadius = params.LABEL_CORNER_RADIUS
        topAndBottomLayer.frame = self.bounds
        topAndBottomLayer.colors = [
            params.BACKGROUND_COLOR.darker().cgColor,
            params.NORMAL_BUTTON_BGCOLOR.cgColor,
            params.NORMAL_BUTTON_BGCOLOR.cgColor,
            params.BACKGROUND_COLOR.brighter().cgColor
        ]
        topAndBottomLayer.locations = [
            0,
            0.15,
            0.85,
            1
        ]
        topAndBottomLayer.opacity = 1
        layer.insertSublayer(topAndBottomLayer, at: 0)
    }
    private func putLeftAndRight() {
        leftAndRightLayer.cornerRadius = params.LABEL_CORNER_RADIUS
        leftAndRightLayer.frame = self.bounds
        leftAndRightLayer.colors = [
            params.BACKGROUND_COLOR.darker().cgColor,
            params.NORMAL_BUTTON_BGCOLOR.cgColor,
            params.NORMAL_BUTTON_BGCOLOR.cgColor,
            params.BACKGROUND_COLOR.brighter().cgColor
        ]
        leftAndRightLayer.locations = [
            0,
            0.05,
            0.95,
            1
        ]
        leftAndRightLayer.startPoint = CGPoint(x: 0, y: 0)
        leftAndRightLayer.endPoint = CGPoint(x: 1, y: 0)
        leftAndRightLayer.opacity = 0.5
        layer.insertSublayer(leftAndRightLayer, at: 1)
    }
    func putUILabel(text: String) {
        calcResultLabel.text = text
        calcResultLabel.frame = CGRect(
            x: 15,
            y: 0,
            width: self.bounds.width * 0.9,
            height: self.bounds.height
        )
        calcResultLabel.textAlignment = .right
        calcResultLabel.font = UIFont(name: "Roboto-Bold", size: 30)
    }
}
```

## コードのざっくりとした解説
storyboardで指定するためのクラス`NeumorLabel.swift`を作成しました。
`CAGradientLayer`をただ上につけるだけだと文字が隠れちゃって表示されない現象が起きてしまったので，`putUILabel()`でさらに上に`UILabel`を載せられるようにしています。（ViewControllerから`putUILabel()`は参照しています）
Layerは影と光を表現するために使っています。左ななめ上から光があたってる感じになっているます。それぞれ白色と黒色で再現しています。

# NeumorSquareButton
練習で円いボタンも作成していたので，名前にわざと"Square"がついています。

## コード全体
```swift/NeumorSquareButton.swift
import Foundation
import UIKit

let FONT_SIZE = 16

class NeumorSquareButton: UIButton {
    override init(frame: CGRect){
        super.init(frame: frame)
        button()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        button()
    }
    private let params = Params()
    
    let highlightLayer = CALayer(),
        shadowLayer = CALayer(),
        textLayer = CATextLayer()
    
    func button() {
        setTitleColor(UIColor.clear, for: .normal)
        textLayer.string = currentTitle
        textLayer.foregroundColor = UIColor.gray.cgColor
        textLayer.contentsScale = UIScreen.main.scale
        textLayer.fontSize = CGFloat(FONT_SIZE)
        textLayer.alignmentMode = .center
        
        [highlightLayer, shadowLayer, textLayer].forEach {
            $0.masksToBounds = false
            $0.frame = layer.bounds
        }
        
        textLayer.frame = CGRect(
            x: CGFloat(0),
            y: self.bounds.height/2 - CGFloat(FONT_SIZE/2),
            width: self.bounds.width,
            height: CGFloat(FONT_SIZE)
        )
        
        putHighlight()
        putShadow()

        layer.addSublayer(shadowLayer)
        layer.addSublayer(highlightLayer)
        layer.addSublayer(textLayer)
    }

    private func putHighlight() {
        highlightLayer.backgroundColor = params.NORMAL_BUTTON_BGCOLOR.cgColor
        highlightLayer.shadowColor = params.BACKGROUND_COLOR.brighter().cgColor
        highlightLayer.cornerRadius = layer.frame.size.height * 0.15
        highlightLayer.shadowOpacity = 1
        highlightLayer.shadowOffset = CGSize(width: -6, height: -6)
        highlightLayer.shadowRadius = 10
    }
    private func putShadow() {
        shadowLayer.backgroundColor = UIColor.white.cgColor
        shadowLayer.shadowColor = UIColor.black.cgColor
        shadowLayer.cornerRadius = layer.frame.size.height * 0.15
        shadowLayer.shadowOpacity = 0.15
        shadowLayer.shadowOffset = CGSize(width: 6, height: 6)
        shadowLayer.shadowRadius = 10
    }
    
    func buttonPush() {
        highlightLayer.shadowOpacity = 0
        shadowLayer.shadowOpacity = 0

        let frame = CGRect(x: 0, y: 0, width: layer.frame.size.width, height: layer.frame.size.height)
        let gradColors = [
            params.BACKGROUND_COLOR.darker().cgColor,
            params.NORMAL_BUTTON_BGCOLOR.cgColor,
            params.NORMAL_BUTTON_BGCOLOR.cgColor,
            params.BACKGROUND_COLOR.brighter().cgColor
        ]
        let gradLocations = [0, 0.15, 0.85, 1]
        let shadowOpacity: Float = 0.5
        let cornerRadius = layer.frame.size.height * 0.15
        
        let virticalGradLayer = CAGradientLayer()
            virticalGradLayer.opacity = shadowOpacity
            virticalGradLayer.frame = frame
            virticalGradLayer.colors = gradColors
            virticalGradLayer.locations = gradLocations as [NSNumber]
            virticalGradLayer.cornerRadius = cornerRadius
        let horizonGradLayer = CAGradientLayer()
            horizonGradLayer.opacity = shadowOpacity
            horizonGradLayer.frame = frame
            horizonGradLayer.startPoint = CGPoint(x: 0, y: 0)
            horizonGradLayer.endPoint = CGPoint(x: 1, y: 0)
            horizonGradLayer.colors = gradColors
            horizonGradLayer.locations = gradLocations as [NSNumber]
            horizonGradLayer.cornerRadius = cornerRadius
        
        layer.addSublayer(horizonGradLayer)
        layer.addSublayer(virticalGradLayer)
        layer.addSublayer(textLayer)
    }
}
```

## コードのざっくりとした解説
押されたときのアニメーションを地道につけていってます...
押されたときのアニメーション↓
> https://www.youtube.com/watch?v=gtr46NjJOls

Labelのときと同じように影と光を配置しています。

# おわりに
ただ電卓を公開したかっただけだったので，一応githubのリポジトリを貼っておきます。
https://github.com/Papillon6814/neumor-calculator/blob/master/README.md