얼굴의 두개골을 그려낼 커스텀 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.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()
}
}
모델과 컨트롤러를 만들자
- 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
}
}
얼굴에 제스처를 적용해 보자
- 핀 포인트를 이용하여 표정을 확대 축소 가능하도록 기능 구현
- 스와이프를 이용하여 아래로 내리면 점점 우는 표정, 위로 올리면 점점 웃는 표정이 되도록 기능 구현
- 탭을 이용하여 터치시 눈을 감거나 뜨도록 기능 구현
- 스토리 보드에 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 구성 상 아이패드로 작업을 해서 실행해야 한다.
위에서 만든 프로그램을 아이폰에서도 실행 가능하도록 구현하기
- 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()
}
}
'iOS-Study' 카테고리의 다른 글
iOS Study : 11주차 - 계산기 클론 코딩 (0) | 2023.01.19 |
---|---|
iOS Study : 11주차 - 기초 문법 공부 (0) | 2023.01.19 |
iOS Study : 10주차 - 오토레이아웃 정리(2) (0) | 2023.01.09 |
iOS Study : 9주차 - 오토레이아웃 정리(1) (0) | 2022.12.28 |
iOS Study : 8주차 - MVVM 디자인 패턴 (0) | 2022.12.26 |