본문 바로가기

iOS-Study

iOS Study : 12주차 - 얼굴앱 만들기

얼굴의 두개골을 그려낼 커스텀 UIView 생성

FaceUIView.swift

import UIKit

//UIView의 서브 클래스인 FaceView
//얼굴의 두개골을 그려내기 위해 커스텀한 UIView
class FaceView: UIView {

    override func draw(_ rect: CGRect)
    {
        //얼굴 반경
        let skullRadius = min(bounds.size.width, bounds.size.height) / 2    //bounds = 나의 좌표시스템 안에서 그릴 직사각형
        
        //얼굴 중심
        let skullCenter = CGPoint(x: bounds.midX, y: bounds.midY)
        
        //startAngle, endAngle은 라디안으로 표기하기 때문에 π로 표시 / clockwise는 시계, 반시계 방향 정함
        let skull = UIBezierPath(arcCenter: skullCenter, radius: skullRadius, startAngle: 0.0, endAngle: CGFloat(2*Double.pi), clockwise: false)
        skull.lineWidth = 5.0   //선 굵기
        UIColor.blue.set()    //색 지정
        skull.stroke()  //위에서 설정한대로 그리기
    }

}

 

FaceView와 스토리보드 연결

  • 스토리보드에 UIView를 넣고 크기를 채운 다음 Reset to Suggeested Constraints 클릭
  • Inspector 창에서 FaceView로 바꿈
  • Attribute Inspector에서 content Mode를 Redraw로 설정

실행하면 FaceView에서 작성한 대로 얼굴형이 그려진다.

눈과 입을 그려넣기

FaceView.swift

import UIKit

//UIView의 서브 클래스인 FaceView
//얼굴의 두개골을 그려내기 위해 커스텀한 UIView
class FaceView: UIView {
    
    var scale: CGFloat = 0.90
    
    //굽은 비율
    var mouthCurvature: Double = 1.0    //1 = 스마일, -1 우는 표정
    
    //얼굴 반경, 연산이 되도록 계산 프로퍼티로 변경
    private var skullRadius: CGFloat {
        return min(bounds.size.width, bounds.size.height) / 2 * scale  //bounds = 나의 좌표시스템 안에서 그릴
    }
    //얼굴 중심
    private var skullCenter:CGPoint {
        return CGPoint(x: bounds.midX, y: bounds.midY)
    }
    
    //비율 구조체
    private struct Ratios {
        static  let SkullRadiusToEyeOffset: CGFloat = 3     //눈 사이 비율
        static let SkullRadiusToEyeRadius: CGFloat = 10    //눈 반경
        static let SkullRadiusToMouthWidth: CGFloat = 1    //입 너비
        static let SkullRadiusToMouthHeight: CGFloat = 3   //입 높이
        static let SkullRadiusToMouthOffset: CGFloat = 3
    }
    
    //눈에 대해 설명하는 enum
    private enum Eye {
        case Left
        case Right
    }
    
    //centerPoint와 Radius를 받아서 BezierPath를 반환하는 함수
    private func pathForCircleCenteredAtPoint(midPoint: CGPoint, widthRadius radius: CGFloat) -> UIBezierPath
    {
        //startAngle, endAngle은 라디안으로 표기하기 때문에 π로 표시 / clockwise는 시계, 반시계 방향 정함
        let path = UIBezierPath(
            arcCenter: midPoint,
            radius: radius,
            startAngle: 0.0,
            endAngle: CGFloat(2*Double.pi),
            clockwise: false)
        path.lineWidth = 5.0   //선 굵기
        return path
    }
    
    //눈의 중심을 얻기 위한 함수
    private func getEyeCenter(eye: Eye) -> CGPoint
    {
        let eyeOffset = skullRadius / Ratios.SkullRadiusToEyeOffset
        var eyeCenter = skullCenter     //일단 눈의 중심을 얼굴과 맞춤
        eyeCenter.y -= eyeOffset     //y축에서 -는 올라감
        switch eye {
        case .Left: eyeCenter.x -= eyeOffset    //왼쪽 눈일 경우 눈을 왼쪽으로 이동
        case .Right: eyeCenter.x += eyeOffset   //오른쪽 눈일 경우 눈을 오른쪽으로 이동
        }
        
        return eyeCenter
    }
    
    private func pathForEye(eye: Eye) -> UIBezierPath
    {
        //눈의 반경
        let eyeRadius = skullRadius / Ratios.SkullRadiusToEyeRadius
        
        //눈의 중심
        let eyeCenter = getEyeCenter(eye: eye)
        
        return pathForCircleCenteredAtPoint(midPoint: eyeCenter, widthRadius: eyeRadius)
    }
    
    //입 생성하는 함수
    private func pathForMouth() -> UIBezierPath
    {
        let mouthWidth = skullRadius / Ratios.SkullRadiusToMouthWidth
        let mouthHeight = skullRadius / Ratios.SkullRadiusToMouthHeight
        let mouthOffset = skullRadius / Ratios.SkullRadiusToEyeOffset
        
        //입을 그리기 위해 입 위치에 직사각형 생성
        let mouthRect = CGRect(x: skullCenter.x - mouthWidth/2, y: skullCenter.y + mouthOffset, width: mouthWidth, height: mouthHeight)
        
        let smileOffset = CGFloat(max(-1, min(mouthCurvature, 1))) * mouthRect.height
        let start = CGPoint(x: mouthRect.minX, y: mouthRect.minY)
        let end = CGPoint(x: mouthRect.maxX, y: mouthRect.minY)
        let cp1 = CGPoint(x: mouthRect.minX + mouthRect.width/3, y: mouthRect.minY + smileOffset)
        let cp2 = CGPoint(x: mouthRect.maxX - mouthRect.width/3, y: mouthRect.minY + smileOffset)
        
        //Bezier Curve = 두 포인트 사이에 그린 선, 시작과 끝 두개의 포인트를 정해야 한다.
        let path = UIBezierPath()
        path.move(to: start)
        path.addCurve(to: end, controlPoint1: cp1, controlPoint2: cp2)
        path.lineWidth = 5.0
        
        return path
    }

    override func draw(_ rect: CGRect)
    {
        UIColor.blue.set()    //색 지정
        pathForCircleCenteredAtPoint(midPoint: skullCenter, widthRadius: skullRadius).stroke()  //얼굴 그리기
        pathForEye(eye: .Left).stroke()
        pathForEye(eye: .Right).stroke()
        pathForMouth().stroke()
    }

}

 

눈썹을 추가하고 다양한 얼굴 표정 만들기

FaceView.swift

import UIKit

//UIView의 서브 클래스인 FaceView
//얼굴의 두개골을 그려내기 위해 커스텀한 UIView
@IBDesignable   //스토리보드에서 코드를 실행하지 않아도 자동으로 보임
class FaceView: UIView {
    
    @IBInspectable  //지정한 매개변수들을 Inspector창에서 볼 수 있고 지정도 가능
    var scale: CGFloat = 0.90 { didSet { setNeedsDisplay() }}    //값들이 변했을 때 뷰가 다시 그려질수 있도록
    
    //굽은 비율
    @IBInspectable
    var mouthCurvature: Double = 1.0 { didSet { setNeedsDisplay() }}    //1 = 스마일, -1 = 우는 표정
    
    //눈을 감았는지 아닌지
    @IBInspectable
    var eyesOpen: Bool  = false { didSet { setNeedsDisplay() }}
    //눈썹 기울기
    @IBInspectable
    var eyeBrowTilt: Double = -0.5 { didSet { setNeedsDisplay() }}  //-1 = 주름지게, 1 = 완전히 풀리게
    
    //색상
    @IBInspectable
    var color: UIColor = UIColor.blue { didSet { setNeedsDisplay() }}
    
    //선 굵기
    @IBInspectable
    var lineWidth: CGFloat = 5.0 { didSet { setNeedsDisplay() }}
    
    //얼굴 반경, 연산이 되도록 계산 프로퍼티로 변경
    private var skullRadius: CGFloat {
        return min(bounds.size.width, bounds.size.height) / 2 * scale  //bounds = 나의 좌표시스템 안에서 그릴
    }
    //얼굴 중심
    private var skullCenter:CGPoint {
        return CGPoint(x: bounds.midX, y: bounds.midY)
    }
    
    //비율 구조체
    private struct Ratios {
        static  let SkullRadiusToEyeOffset: CGFloat = 3     //눈 사이 비율
        static let SkullRadiusToEyeRadius: CGFloat = 10    //눈 반경
        static let SkullRadiusToMouthWidth: CGFloat = 1    //입 너비
        static let SkullRadiusToMouthHeight: CGFloat = 3   //입 높이
        static let SkullRadiusToMouthOffset: CGFloat = 3
        static let SkullRadiusToBrowOffset: CGFloat = 5
    }
    
    //눈에 대해 설명하는 enum
    private enum Eye {
        case Left
        case Right
    }
    
    //centerPoint와 Radius를 받아서 BezierPath를 반환하는 함수
    private func pathForCircleCenteredAtPoint(midPoint: CGPoint, widthRadius radius: CGFloat) -> UIBezierPath
    {
        //startAngle, endAngle은 라디안으로 표기하기 때문에 π로 표시 / clockwise는 시계, 반시계 방향 정함
        let path = UIBezierPath(
            arcCenter: midPoint,
            radius: radius,
            startAngle: 0.0,
            endAngle: CGFloat(2*Double.pi),
            clockwise: false)
        path.lineWidth = lineWidth   //선 굵기
        return path
    }
    
    //눈의 중심을 얻기 위한 함수
    private func getEyeCenter(eye: Eye) -> CGPoint
    {
        let eyeOffset = skullRadius / Ratios.SkullRadiusToEyeOffset
        var eyeCenter = skullCenter     //일단 눈의 중심을 얼굴과 맞춤
        eyeCenter.y -= eyeOffset     //y축에서 -는 올라감
        switch eye {
        case .Left: eyeCenter.x -= eyeOffset    //왼쪽 눈일 경우 눈을 왼쪽으로 이동
        case .Right: eyeCenter.x += eyeOffset   //오른쪽 눈일 경우 눈을 오른쪽으로 이동
        }
        
        return eyeCenter
    }
    
    private func pathForEye(eye: Eye) -> UIBezierPath
    {
        //눈의 반경
        let eyeRadius = skullRadius / Ratios.SkullRadiusToEyeRadius
        
        //눈의 중심
        let eyeCenter = getEyeCenter(eye: eye)
        if eyesOpen {
            return pathForCircleCenteredAtPoint(midPoint: eyeCenter, widthRadius: eyeRadius)
        }
        else {
            let path = UIBezierPath()
            path.move(to: CGPoint(x: eyeCenter.x - eyeRadius, y: eyeCenter.y))
            path.addLine(to: CGPoint(x: eyeCenter.x + eyeRadius, y: eyeCenter.y))
            path.lineWidth = lineWidth
            return path
        }
    }
    
    //입 생성하는 함수
    private func pathForMouth() -> UIBezierPath
    {
        let mouthWidth = skullRadius / Ratios.SkullRadiusToMouthWidth
        let mouthHeight = skullRadius / Ratios.SkullRadiusToMouthHeight
        let mouthOffset = skullRadius / Ratios.SkullRadiusToEyeOffset
        
        //입을 그리기 위해 입 위치에 직사각형 생성
        let mouthRect = CGRect(x: skullCenter.x - mouthWidth/2, y: skullCenter.y + mouthOffset, width: mouthWidth, height: mouthHeight)
        
        let smileOffset = CGFloat(max(-1, min(mouthCurvature, 1))) * mouthRect.height
        let start = CGPoint(x: mouthRect.minX, y: mouthRect.minY)
        let end = CGPoint(x: mouthRect.maxX, y: mouthRect.minY)
        let cp1 = CGPoint(x: mouthRect.minX + mouthRect.width/3, y: mouthRect.minY + smileOffset)
        let cp2 = CGPoint(x: mouthRect.maxX - mouthRect.width/3, y: mouthRect.minY + smileOffset)
        
        //Bezier Curve = 두 포인트 사이에 그린 선, 시작과 끝 두개의 포인트를 정해야 한다.
        let path = UIBezierPath()
        path.move(to: start)
        path.addCurve(to: end, controlPoint1: cp1, controlPoint2: cp2)
        path.lineWidth = lineWidth
        
        return path
    }
    
    //눈썹을 그리기 위한 함수
    private func pathForBrow(eye: Eye) -> UIBezierPath
    {
        var tilt = eyeBrowTilt
        switch eye {
        case .Left: tilt *= -1.0
        case .Right: break
        }
        
        var browCenter = getEyeCenter(eye: eye)
        browCenter.y -= skullRadius / Ratios.SkullRadiusToBrowOffset
        let eyeRaadius = skullRadius / Ratios.SkullRadiusToEyeRadius
        let tiltOffset = CGFloat(max(-1, min(tilt, 1))) * eyeRaadius / 2
        let browStart = CGPoint(x: browCenter.x - eyeRaadius, y: browCenter.y - tiltOffset)
        let browEnd = CGPoint(x: browCenter.x + eyeRaadius, y: browCenter.y + tiltOffset)
        let path = UIBezierPath()
        path.move(to: browStart)
        path.addLine(to: browEnd)
        path.lineWidth = lineWidth
        return path
    }

    override func draw(_ rect: CGRect)
    {
        color.set()    //색 지정
        pathForCircleCenteredAtPoint(midPoint: skullCenter, widthRadius: skullRadius).stroke()  //얼굴 그리기
        pathForEye(eye: .Left).stroke()
        pathForEye(eye: .Right).stroke()
        pathForMouth().stroke()
        pathForBrow(eye: .Left).stroke()
        pathForBrow(eye: .Right).stroke()
    }

}

FaceView의 코드들을 스토리보드에서 볼 수 있고 public으로 설정한 매개변수들을 Inspector창에서 변경 가능하다.

 

모델과 컨트롤러를 만들자

  • Model을 만들어서 Controller에서 뷰에 보여지게 하도록 할 것이다.
  • Model을 맡은 FaicalExpression.swift파일을 만들어서 코드를 작성하고 FaceViewController.swift 파일을 작성한다.

FaicalExpression.swift

//Model
import Foundation

struct FacialExpression
{
    enum Eyes: Int {
        case Open
        case Closed
        case Squinting  //가늘게 뜬
    }
    
    enum EyeBrows: Int {
        case Relaxed    //편안한
        case Normal     //일반
        case Furrowed   //주름진
        
        func moreRelaxedVrow() -> EyeBrows {
            return EyeBrows(rawValue: rawValue - 1) ?? .Relaxed
        }
        func moreFurrowedVrow() -> EyeBrows {
            return EyeBrows(rawValue: rawValue + 1) ?? .Furrowed
        }
    }
    
    enum Mouth: Int {
        case Frown  //찌푸린
        case Smirk  //실실웃는
        case Newtral    //평상시
        case Grin   //활짝웃는
        case Smile  //미소짓는
        
        func sadderMouth() -> Mouth {
            return Mouth(rawValue: rawValue - 1) ?? .Frown
        }
        func happierMouth() -> Mouth {
            return Mouth(rawValue: rawValue + 1) ?? .Smile
        }
    }
    
    var eyes: Eyes
    var eyebrows: EyeBrows
    var mouth: Mouth
}

 

FaceViewController.swift

import UIKit

class FaceViewController: UIViewController {
    
    var expression = FacialExpression(eyes: .Closed, eyebrows: .Relaxed, mouth: .Smirk) {
        didSet {
            updataUI()
        }
    }
    
    @IBOutlet weak var faceView: FaceView! {
        didSet {
            updataUI()
        }
    }
    
    //입 모양 딕셔너리
    private var mouthCurvatures = [FacialExpression.Mouth.Frown: -1.0, .Grin: 0.5, .Smile: 1.0, .Smirk: -0.5, .Newtral: 0.0 ]
    
    //눈썹 모양 딕셔너리
    private var eyeBrowTilts = [FacialExpression.EyeBrows.Normal: 0.0, .Furrowed: -0.5, .Relaxed: 0.5 ]
    
    //표정이 변하면 적용시키기 위한 함수
    private func updataUI() {
        
        //눈
        switch expression.eyes {
        case .Open: faceView.eyesOpen = true
        case .Closed: faceView.eyesOpen = false
        case .Squinting: faceView.eyesOpen = false
        }
    
        //입
        faceView.mouthCurvature = mouthCurvatures[expression.mouth] ?? 0.0
        
        //눈썹
        faceView.eyeBrowTilt = eyeBrowTilts[expression.eyebrows] ?? 0.0
    
    }
}

스토리보드에 설정한 표정과 다르게 실행을 하면 FaceViewController에서 설정한 표정으로 실행되게 된다.

 

얼굴에 제스처를 적용해 보자

  • 핀 포인트를 이용하여 표정을 확대 축소 가능하도록 기능 구현
  • 스와이프를 이용하여 아래로 내리면 점점 우는 표정, 위로 올리면 점점 웃는 표정이 되도록 기능 구현
  • 탭을 이용하여 터치시 눈을 감거나 뜨도록 기능 구현
    • 스토리 보드에 Tap Gesture Recognizer를 추가하여 FaceViewController에 드래그하여 추가

FaceView.swift에 핀치를 이용한 기능 코드 추가

//핀치를 이용하여 확대하거나 축소하는 기능
    @objc func changeScale(recognizer: UIPinchGestureRecognizer) {
        switch recognizer.state {
        case .changed, .ended:
            scale *= recognizer.scale
            recognizer.scale = 1.0
        default:
            break
        }
    }

 

FaceViewController.swift에서 기능들 구현

import UIKit

class FaceViewController: UIViewController {
    
    var expression = FacialExpression(eyes: .Closed, eyebrows: .Relaxed, mouth: .Smirk) {
        didSet {
            updataUI()
        }
    }
    
    @IBOutlet weak var faceView: FaceView! {
        didSet {
            //핀치를 이용하여 확대하거나 축소하는 함수는 불러와 사용
            faceView.addGestureRecognizer(UIPinchGestureRecognizer(
                target: faceView, action: #selector(FaceView.changeScale(recognizer:))
            ))
            
            //스와이프 제스처를 통해 표정 바꾸기
            //위로 올리면 웃음짓기
            let happierSwipeGestureRecognizer = UISwipeGestureRecognizer(
                target: self, action: #selector(FaceViewController.increaseHappiness)
            )
            happierSwipeGestureRecognizer.direction = .up
            faceView.addGestureRecognizer(happierSwipeGestureRecognizer)
            
            //아래로 내리면 슬픈 표정
            let sadderSwipeGestureRecognizer = UISwipeGestureRecognizer(
                target: self, action: #selector(FaceViewController.decreaseHappiness)
            )
            sadderSwipeGestureRecognizer.direction = .down
            faceView.addGestureRecognizer(sadderSwipeGestureRecognizer)
            
            updataUI()
        }
    }
    
    //표정 바꾸는 함수
    @objc func increaseHappiness() {
        expression.mouth = expression.mouth.happierMouth()
    }
    @objc func decreaseHappiness() {
        expression.mouth = expression.mouth.sadderMouth()
    }
    
    //터치시 눈을 뜨거나 감게 하는 기능
    @IBAction func toggleEyes(_ recognizer: UITapGestureRecognizer) {
        if recognizer.state == .ended {
            switch expression.eyes {
            case .Open: expression.eyes = .Closed
            case .Closed: expression.eyes = .Open
            case .Squinting: break
            }
        }
    }
    
    //입 모양 딕셔너리
    private var mouthCurvatures = [FacialExpression.Mouth.Frown: -1.0, .Grin: 0.5, .Smile: 1.0, .Smirk: -0.5, .Newtral: 0.0 ]
    
    //눈썹 모양 딕셔너리
    private var eyeBrowTilts = [FacialExpression.EyeBrows.Normal: 0.0, .Furrowed: -0.5, .Relaxed: 0.5 ]
    
    //표정이 변하면 적용시키기 위한 함수
    private func updataUI() {
        
        //눈
        switch expression.eyes {
        case .Open: faceView.eyesOpen = true
        case .Closed: faceView.eyesOpen = false
        case .Squinting: faceView.eyesOpen = false
        }
    
        //입
        faceView.mouthCurvature = mouthCurvatures[expression.mouth] ?? 0.0
        
        //눈썹
        faceView.eyeBrowTilt = eyeBrowTilts[expression.eyebrows] ?? 0.0
    
    }
}

 

새로운 MVC를 생성해서 감정 리스트를 나열하여 클릭시 표정 바꾸기

  • View Controller와 EmotionsViewController.swift파일을 추가하여 서로 연결을 해 주고 버튼 4개를 만들어 준다.
  • 만들어준 버튼을 앵커로 고정하여 화면이 바뀌어도 중앙에 나오도록 한다.
  • Split View Controller도 추가하여 EmotionsViewController와 연결된 뷰에 드래그 하여 primary view controller
    FaceViewController와 연결된 뷰에는 second view controller를 선택하여 segue를 연결해 준다.
  •  버튼이 있는 뷰에서 버튼을 클릭하여 FaceViewController와 연결된 뷰에 드래그 하여 Show detail로 segue를 연결해 준다.
    연결한 segue를 클릭하여 identifier를 넣어주고 나머지 버튼도 동일하게 작업한다.

FaceViewController.swift

  • 버튼 클릭시 표정이 변할 때 nil값이 불러오는 경우를 대비해서 updateUI() 함수를 수정한다.
//표정이 변하면 적용시키기 위한 함수
    private func updataUI() {
        
        if faceView != nil {
            //눈
            switch expression.eyes {
            case .Open: faceView.eyesOpen = true
            case .Closed: faceView.eyesOpen = false
            case .Squinting: faceView.eyesOpen = false
            }
            
            //입
            faceView.mouthCurvature = mouthCurvatures[expression.mouth] ?? 0.0
            
            //눈썹
            faceView.eyeBrowTilt = eyeBrowTilts[expression.eyebrows] ?? 0.0
        }
    }

 

EmotionalsViewController.swift

  • 버튼이 클릭될 때 마다 표정이 바뀌는 코드를 작성
import UIKit

class EmotionsViewController: UIViewController {
    
    //identifier에 맞게 표정을 설정해주는 딕셔너리
    private let emotionalFaces: Dictionary<String, FacialExpression> = [
        "angry" : FacialExpression(eyes: .Closed, eyebrows: .Furrowed, mouth: .Frown),
        "happy" : FacialExpression(eyes: .Open, eyebrows: .Normal, mouth: .Smile),
        "worried" : FacialExpression(eyes: .Open, eyebrows: .Relaxed, mouth: .Smirk),
        "mischievious" : FacialExpression(eyes: .Open, eyebrows: .Furrowed, mouth: .Grin)
    ]
    
    //목적지가 되는 destiantionViewController를 연결하는 역할 - destinationViewController = FaceViewController
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let destinationvc = segue.destination
        if let facevc = destinationvc as? FaceViewController {
            if let identifier = segue.identifier {
                if let expression = emotionalFaces[identifier] {
                    facevc.expression = expression
                }
            }
        }
    }

}

 

실행

  • Split View Controller 구성 상 아이패드로 작업을 해서 실행해야 한다.

Angry버튼을 누르면 화난 표정이, Happy 표정을 누르면 미소짓는 표정이 나타난다.

 

위에서 만든 프로그램을 아이폰에서도 실행 가능하도록 구현하기

  • EmotionsViewController가 연결된 뷰를 클릭한 후 Editor - Embed in - Navigation Controller를 클릭한다.
  • FaceViewController가 연결된 뷰도 똑같이 해 준다.
  • EmotiosViewController.swift 파일에서 코드를 수정해 준다.

 

EmotionsViewController.swift

  • 아이폰과 아이패드 둘 다에서 잘 실행이 되고, 버튼 클릭시 표정의 이름도 뜨게 코드를 수정한다.
import UIKit

class EmotionsViewController: UIViewController {
    
    //identifier에 맞게 표정을 설정해주는 딕셔너리
    private let emotionalFaces: Dictionary<String, FacialExpression> = [
        "angry" : FacialExpression(eyes: .Closed, eyebrows: .Furrowed, mouth: .Frown),
        "happy" : FacialExpression(eyes: .Open, eyebrows: .Normal, mouth: .Smile),
        "worried" : FacialExpression(eyes: .Open, eyebrows: .Relaxed, mouth: .Smirk),
        "mischievious" : FacialExpression(eyes: .Open, eyebrows: .Furrowed, mouth: .Grin)
    ]
    
    //목적지가 되는 destiantionViewController를 연결하는 역할 - destinationViewController = FaceViewController
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        var destinationvc = segue.destination
        if let navcon = destinationvc as? UINavigationController {
            destinationvc = navcon.visibleViewController ?? destinationvc
        }
        if let facevc = destinationvc as? FaceViewController {
            if let identifier = segue.identifier {
                if let expression = emotionalFaces[identifier] {
                    facevc.expression = expression
                    
                    //버튼 클릭시 표정의 이름이 표정 위에 뜨도록 하기 위함
                    if let sendingButton = sender as? UIButton {
                        facevc.navigationItem.title = segue.identifier
//                        facevc.navigationItem.title = sendingButton.currentTitle
                    }
                }
            }
        }
    }

}

 

 

완성된 코드

FaceViewController.swift

더보기
import UIKit

class FaceViewController: UIViewController {
    
    var expression = FacialExpression(eyes: .Closed, eyebrows: .Relaxed, mouth: .Smirk) {
        didSet {
            updataUI()
        }
    }
    
    @IBOutlet weak var faceView: FaceView! {
        didSet {
            //핀치를 이용하여 확대하거나 축소하는 함수는 불러와 사용
            faceView.addGestureRecognizer(UIPinchGestureRecognizer(
                target: faceView, action: #selector(FaceView.changeScale(recognizer:))
            ))
            
            //스와이프 제스처를 통해 표정 바꾸기
            //위로 올리면 웃음짓기
            let happierSwipeGestureRecognizer = UISwipeGestureRecognizer(
                target: self, action: #selector(FaceViewController.increaseHappiness)
            )
            happierSwipeGestureRecognizer.direction = .up
            faceView.addGestureRecognizer(happierSwipeGestureRecognizer)
            
            //아래로 내리면 슬픈 표정
            let sadderSwipeGestureRecognizer = UISwipeGestureRecognizer(
                target: self, action: #selector(FaceViewController.decreaseHappiness)
            )
            sadderSwipeGestureRecognizer.direction = .down
            faceView.addGestureRecognizer(sadderSwipeGestureRecognizer)
            
            updataUI()
        }
    }
    
    //표정 바꾸는 함수
    @objc func increaseHappiness() {
        expression.mouth = expression.mouth.happierMouth()
    }
    @objc func decreaseHappiness() {
        expression.mouth = expression.mouth.sadderMouth()
    }
    
    //터치시 눈을 뜨거나 감게 하는 기능
    @IBAction func toggleEyes(_ recognizer: UITapGestureRecognizer) {
        if recognizer.state == .ended {
            switch expression.eyes {
            case .Open: expression.eyes = .Closed
            case .Closed: expression.eyes = .Open
            case .Squinting: break
            }
        }
    }
    
    //입 모양 딕셔너리
    private var mouthCurvatures = [FacialExpression.Mouth.Frown: -1.0, .Grin: 0.5, .Smile: 1.0, .Smirk: -0.5, .Newtral: 0.0 ]
    
    //눈썹 모양 딕셔너리
    private var eyeBrowTilts = [FacialExpression.EyeBrows.Normal: 0.0, .Furrowed: -0.5, .Relaxed: 0.5 ]
    
    //표정이 변하면 적용시키기 위한 함수
    private func updataUI() {
        
        if faceView != nil {
            //눈
            switch expression.eyes {
            case .Open: faceView.eyesOpen = true
            case .Closed: faceView.eyesOpen = false
            case .Squinting: faceView.eyesOpen = false
            }
            
            //입
            faceView.mouthCurvature = mouthCurvatures[expression.mouth] ?? 0.0
            
            //눈썹
            faceView.eyeBrowTilt = eyeBrowTilts[expression.eyebrows] ?? 0.0
        }
    }
}

 

EmotionsViewController.swift

더보기
import UIKit

class EmotionsViewController: UIViewController {
    
    //identifier에 맞게 표정을 설정해주는 딕셔너리
    private let emotionalFaces: Dictionary<String, FacialExpression> = [
        "angry" : FacialExpression(eyes: .Closed, eyebrows: .Furrowed, mouth: .Frown),
        "happy" : FacialExpression(eyes: .Open, eyebrows: .Normal, mouth: .Smile),
        "worried" : FacialExpression(eyes: .Open, eyebrows: .Relaxed, mouth: .Smirk),
        "mischievious" : FacialExpression(eyes: .Open, eyebrows: .Furrowed, mouth: .Grin)
    ]
    
    //목적지가 되는 destiantionViewController를 연결하는 역할 - destinationViewController = FaceViewController
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        var destinationvc = segue.destination
        if let navcon = destinationvc as? UINavigationController {
            destinationvc = navcon.visibleViewController ?? destinationvc
        }
        if let facevc = destinationvc as? FaceViewController {
            if let identifier = segue.identifier {
                if let expression = emotionalFaces[identifier] {
                    facevc.expression = expression
                    
                    //버튼 클릭시 표정의 이름이 표정 위에 뜨도록 하기 위함
                    if let sendingButton = sender as? UIButton {
                        facevc.navigationItem.title = segue.identifier
//                        facevc.navigationItem.title = sendingButton.currentTitle
                    }
                }
            }
        }
    }

}

 

FacialExpression.swift

더보기
//Model
import Foundation

struct FacialExpression
{
    enum Eyes: Int {
        case Open
        case Closed
        case Squinting  //가늘게 뜬
    }
    
    enum EyeBrows: Int {
        case Relaxed    //편안한
        case Normal     //일반
        case Furrowed   //주름진
        
        func moreRelaxedVrow() -> EyeBrows {
            return EyeBrows(rawValue: rawValue - 1) ?? .Relaxed
        }
        func moreFurrowedVrow() -> EyeBrows {
            return EyeBrows(rawValue: rawValue + 1) ?? .Furrowed
        }
    }
    
    enum Mouth: Int {
        case Frown  //찌푸린
        case Smirk  //실실웃는
        case Newtral    //평상시
        case Grin   //활짝웃는
        case Smile  //미소짓는
        
        func sadderMouth() -> Mouth {
            return Mouth(rawValue: rawValue - 1) ?? .Frown
        }
        func happierMouth() -> Mouth {
            return Mouth(rawValue: rawValue + 1) ?? .Smile
        }
    }
    
    var eyes: Eyes
    var eyebrows: EyeBrows
    var mouth: Mouth
}

 

FaceView.swift

더보기
import UIKit

//UIView의 서브 클래스인 FaceView
//얼굴의 두개골을 그려내기 위해 커스텀한 UIView
@IBDesignable   //스토리보드에서 코드를 실행하지 않아도 자동으로 보임
class FaceView: UIView {
    
    @IBInspectable  //지정한 매개변수들을 Inspector창에서 볼 수 있고 지정도 가능
    var scale: CGFloat = 0.90 { didSet { setNeedsDisplay() }}    //값들이 변했을 때 뷰가 다시 그려질수 있도록
    
    //굽은 비율
    @IBInspectable
    var mouthCurvature: Double = 1.0 { didSet { setNeedsDisplay() }}    //1 = 스마일, -1 = 우는 표정
    
    //눈을 감았는지 아닌지
    @IBInspectable
    var eyesOpen: Bool  = true { didSet { setNeedsDisplay() }}
    //눈썹 기울기
    @IBInspectable
    var eyeBrowTilt: Double = 0.0 { didSet { setNeedsDisplay() }}  //-1 = 주름지게, 1 = 완전히 풀리게
    
    //색상
    @IBInspectable
    var color: UIColor = UIColor.blue { didSet { setNeedsDisplay() }}
    
    //선 굵기
    @IBInspectable
    var lineWidth: CGFloat = 5.0 { didSet { setNeedsDisplay() }}
    
    //핀치를 이용하여 확대하거나 축소하는 기능
    @objc func changeScale(recognizer: UIPinchGestureRecognizer) {
        switch recognizer.state {
        case .changed, .ended:
            scale *= recognizer.scale
            recognizer.scale = 1.0
        default:
            break
        }
    }
    
    //얼굴 반경, 연산이 되도록 계산 프로퍼티로 변경
    private var skullRadius: CGFloat {
        return min(bounds.size.width, bounds.size.height) / 2 * scale  //bounds = 나의 좌표시스템 안에서 그릴
    }
    //얼굴 중심
    private var skullCenter:CGPoint {
        return CGPoint(x: bounds.midX, y: bounds.midY)
    }
    
    //비율 구조체
    private struct Ratios {
        static  let SkullRadiusToEyeOffset: CGFloat = 3     //눈 사이 비율
        static let SkullRadiusToEyeRadius: CGFloat = 10    //눈 반경
        static let SkullRadiusToMouthWidth: CGFloat = 1    //입 너비
        static let SkullRadiusToMouthHeight: CGFloat = 3   //입 높이
        static let SkullRadiusToMouthOffset: CGFloat = 3
        static let SkullRadiusToBrowOffset: CGFloat = 5
    }
    
    //눈에 대해 설명하는 enum
    private enum Eye {
        case Left
        case Right
    }
    
    //centerPoint와 Radius를 받아서 BezierPath를 반환하는 함수
    private func pathForCircleCenteredAtPoint(midPoint: CGPoint, widthRadius radius: CGFloat) -> UIBezierPath
    {
        //startAngle, endAngle은 라디안으로 표기하기 때문에 π로 표시 / clockwise는 시계, 반시계 방향 정함
        let path = UIBezierPath(
            arcCenter: midPoint,
            radius: radius,
            startAngle: 0.0,
            endAngle: CGFloat(2*Double.pi),
            clockwise: false)
        path.lineWidth = lineWidth   //선 굵기
        return path
    }
    
    //눈의 중심을 얻기 위한 함수
    private func getEyeCenter(eye: Eye) -> CGPoint
    {
        let eyeOffset = skullRadius / Ratios.SkullRadiusToEyeOffset
        var eyeCenter = skullCenter     //일단 눈의 중심을 얼굴과 맞춤
        eyeCenter.y -= eyeOffset     //y축에서 -는 올라감
        switch eye {
        case .Left: eyeCenter.x -= eyeOffset    //왼쪽 눈일 경우 눈을 왼쪽으로 이동
        case .Right: eyeCenter.x += eyeOffset   //오른쪽 눈일 경우 눈을 오른쪽으로 이동
        }
        
        return eyeCenter
    }
    
    private func pathForEye(eye: Eye) -> UIBezierPath
    {
        //눈의 반경
        let eyeRadius = skullRadius / Ratios.SkullRadiusToEyeRadius
        
        //눈의 중심
        let eyeCenter = getEyeCenter(eye: eye)
        if eyesOpen {
            return pathForCircleCenteredAtPoint(midPoint: eyeCenter, widthRadius: eyeRadius)
        }
        else {
            let path = UIBezierPath()
            path.move(to: CGPoint(x: eyeCenter.x - eyeRadius, y: eyeCenter.y))
            path.addLine(to: CGPoint(x: eyeCenter.x + eyeRadius, y: eyeCenter.y))
            path.lineWidth = lineWidth
            return path
        }
    }
    
    //입 생성하는 함수
    private func pathForMouth() -> UIBezierPath
    {
        let mouthWidth = skullRadius / Ratios.SkullRadiusToMouthWidth
        let mouthHeight = skullRadius / Ratios.SkullRadiusToMouthHeight
        let mouthOffset = skullRadius / Ratios.SkullRadiusToEyeOffset
        
        //입을 그리기 위해 입 위치에 직사각형 생성
        let mouthRect = CGRect(x: skullCenter.x - mouthWidth/2, y: skullCenter.y + mouthOffset, width: mouthWidth, height: mouthHeight)
        
        let smileOffset = CGFloat(max(-1, min(mouthCurvature, 1))) * mouthRect.height
        let start = CGPoint(x: mouthRect.minX, y: mouthRect.minY)
        let end = CGPoint(x: mouthRect.maxX, y: mouthRect.minY)
        let cp1 = CGPoint(x: mouthRect.minX + mouthRect.width/3, y: mouthRect.minY + smileOffset)
        let cp2 = CGPoint(x: mouthRect.maxX - mouthRect.width/3, y: mouthRect.minY + smileOffset)
        
        //Bezier Curve = 두 포인트 사이에 그린 선, 시작과 끝 두개의 포인트를 정해야 한다.
        let path = UIBezierPath()
        path.move(to: start)
        path.addCurve(to: end, controlPoint1: cp1, controlPoint2: cp2)
        path.lineWidth = lineWidth
        
        return path
    }
    
    //눈썹을 그리기 위한 함수
    private func pathForBrow(eye: Eye) -> UIBezierPath
    {
        var tilt = eyeBrowTilt
        switch eye {
        case .Left: tilt *= -1.0
        case .Right: break
        }
        
        var browCenter = getEyeCenter(eye: eye)
        browCenter.y -= skullRadius / Ratios.SkullRadiusToBrowOffset
        let eyeRaadius = skullRadius / Ratios.SkullRadiusToEyeRadius
        let tiltOffset = CGFloat(max(-1, min(tilt, 1))) * eyeRaadius / 2
        let browStart = CGPoint(x: browCenter.x - eyeRaadius, y: browCenter.y - tiltOffset)
        let browEnd = CGPoint(x: browCenter.x + eyeRaadius, y: browCenter.y + tiltOffset)
        let path = UIBezierPath()
        path.move(to: browStart)
        path.addLine(to: browEnd)
        path.lineWidth = lineWidth
        return path
    }

    override func draw(_ rect: CGRect)
    {
        color.set()    //색 지정
        pathForCircleCenteredAtPoint(midPoint: skullCenter, widthRadius: skullRadius).stroke()  //얼굴 그리기
        pathForEye(eye: .Left).stroke()
        pathForEye(eye: .Right).stroke()
        pathForMouth().stroke()
        pathForBrow(eye: .Left).stroke()
        pathForBrow(eye: .Right).stroke()
    }

}