[𧩠Creative Coding] μ€μ 맀λ¬λ € νλ€λ¦¬λ μμ λ§λ€κΈ° (feat. Interactive Developer)
μλ νμΈμ Foma π» μ λλ€!
μ€λμ μ€μ 맀λ¬λ € νλ€λ¦¬λ μμλ₯Ό λ§λ€μ΄ 볼건λ°μ.
μ΄κ±΄ μ κ° μ‘΄κ²½νλ κ°λ°μμ΄κΈ°λ ν μΈν°λν°λΈ λ벨λ‘νΌ κΉμ’ λ―Όλμ μ νλΈ μ±λμ μ¬λΌμ¨ νν λ¦¬μΌ μ€ νλμΈλ°μ.
μμ λΆν° μ΄λ° μ°½μμ μΈ μ½λ©, μμ μ μΈ μ½λ©μ νκ³ μΆμ μμ¬μ΄ λ§μμλλ° μ΄λ² κΈ°νμ κΉμ’ λ―Όλμ λ°λΌμ ꡬνν΄λ³΄λ €κ³ ν©λλ€.
μ μμμ 보며 λλ¦ μ λ°©μλλ‘ Swiftλ₯Ό μ¬μ©ν΄μ ꡬνν΄λ³΄μμ΅λλ€.
λ°λ‘ μμν κ²μ~
View
λ¨Όμ μμμ λΉ¨κ° μ μ UIViewλ‘ μΈν ν΄μ€λλ€.
(μ λ μμ κ°μ΄λ°μ μ μ΄λ¦μ λ£μ΄μ 보μ¬μ€ κ±°κΈ° λλ¬Έμ labelλ λ£μμ΅λλ€.)
let square:UIView = {
let view:UIView = UIView()
view.backgroundColor = .systemCyan
return view
}()
let redDot1:UIView = {
let view:UIView = UIView()
view.backgroundColor = .clear
return view
}()
let redDot2:UIView = {
let view:UIView = UIView()
view.backgroundColor = .clear
return view
}()
let label:UILabel = {
let label:UILabel = UILabel()
label.text = "Fomagran"
label.font = .systemFont(ofSize: 18, weight: .bold)
label.textColor = .white
return label
}()
1. μμ μμ§μ΄κΈ°
μμλ₯Ό ν°μΉν΄μ μμ§μ΄κ² νλ €λ©΄ μ΄λ»κ² ν΄μΌν κΉμ?
λ°λ‘ ν¬μ μ€μ³λΌλ κ²μ μ΄μ©ν΄μ£Όμ΄μΌ ν©λλ€.
μμμ μλμ κ°μ΄ ν¬μ μ€μ³λ₯Ό λ¬μμ€λλ€.
private func setPanGesture() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.dragSquare(_:)))
square.addGestureRecognizer(panGesture)
}
μμλ₯Ό λλκ·Έ νμ λ μλμ κ°μ΄ μ€νμμΌμ£Όλ©΄
@objc func dragSquare(_ sender: UIPanGestureRecognizer) {
let transition = sender.translation(in: square)
let newX = square.center.x + transition.x
let newY = square.center.y + transition.y
square.center = CGPoint(x: newX, y: newY)
sender.setTranslation(CGPoint.zero, in: square)
}
μμλ₯Ό μμ§μΌ μ μκ² λ©λλ€.
2. μμλ₯Ό ν°μΉν λΆλΆ λΉ¨κ° μ μΌλ‘ νμνκΈ°
μ μμμ 보면 μμλ₯Ό ν°μΉν λΆλΆμ λΉ¨κ° μ μ΄ μκΈ°κ³ μ μ΄ μ겨μ 맀λ¬λ € μμ§μ΄μ£ ?
κ·Έλ λ€λ©΄ μ΄λ»κ² μμλ₯Ό κ°μ₯ μ²μμ ν°μΉν λΆλΆμ μμλΌ μ μμκΉμ?
λ°λ‘ μ²μ λλκ·Έλ₯Ό μμν λ μμΉλ₯Ό μμλ΄λ©΄ λ©λλ€.
UIPanGestureRecognizerλ₯Ό μ΄μ©ν΄μ λλκ·Έκ° μμλ λ μμΉλ₯Ό μμλ λλ€.
그리곀 λΉ¨κ°μ 1μ μμ λΉ¨κ°μμΌλ‘ λ°κΏμ€λλ€.
@objc func dragSquare(_ sender: UIPanGestureRecognizer) {
if sender.state == .began {
let location = sender.location(in: view) //λλκ·Έλ₯Ό μμν μμΉ
redDot1.center = location
redDot1.backgroundColor = .red
...
μ΄λ κ² νλ©΄ μλμ κ°μ΄ λλκ·Έλ₯Ό μμν λ μ§μ μ΄ λΉ¨κ°μμΌλ‘ νμλ©λλ€.
2. λ λ²μ§Έ λΉ¨κ°μ λλκ·Έ μμΉμ νμνκΈ°
μμλ₯Ό ν°μΉν λΆλΆμ λΉ¨κ° μ μ νμνλ€λ©΄ λλκ·Έλ₯Ό νλ μμΉμ λ°λΌ νμλλ λΉ¨κ° μ λ 보μ¬μ€μΌκ² μ£ ?
λλκ·Έκ° λ°λ λμ μμΉλ₯Ό μμλ΄μ΄ λΉ¨κ°μ 2μ μμΉλ₯Ό λ°κΏμ€λλ€.
@objc func dragSquare(_ sender: UIPanGestureRecognizer) {
if sender.state == .began {
let location = sender.location(in: view) //λλκ·Έλ₯Ό μμν μμΉ
redDot1.center = location
redDot1.backgroundColor = .red
}else if sender.state == .changed {
redDot2.center = sender.location(in: view)
redDot2.backgroundColor = .red
...
κ·ΈλΌ μλμ κ°μ΄ λλκ·Έλ₯Ό νλ©΄ μ²μ μμΉμ λλκ·Έλ₯Ό νλ μμΉμ λ°λΌ λΉ¨κ°μ λ κ°κ° νμλ©λλ€.
3. λΉ¨κ° μ λ κ° μ μΌλ‘ μκΈ°
μ΄μ λΉ¨κ° μ λ κ°λ₯Ό μ μΌλ‘ μ΄μ΄μ€μΌκ² μ£ ?
μ μ λ§λ€μ΄ μ£ΌκΈ° μν΄μ CAShapeLayerλ‘ λ§λ€μ΄μ€μΌ ν©λλ€.
private var line = CAShapeLayer()
μλμ κ°μ΄ μμκ³Ό λμ μ§μ ν΄μ£Όκ³ μ μΌλ‘ μ΄μ΄μ€λλ€.
private func addLine(start: CGPoint,end:CGPoint) {
line.removeFromSuperlayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
line.lineWidth = 1
view.layer.addSublayer(line)
}
λΉ¨κ°μ 1κ³Ό λΉ¨κ°μ 2μ μ€μ λΆλΆμ μ μΌλ‘ μ΄μ΄μ€λλ€.
@objc func dragSquare(_ sender: UIPanGestureRecognizer) {
if sender.state == .began {
let location = sender.location(in: view) //λλκ·Έλ₯Ό μμν μμΉ
redDot1.center = location
redDot1.backgroundColor = .red
}else if sender.state == .changed {
redDot2.center = sender.location(in: view)
redDot2.backgroundColor = .red
addLine(start: redDot1.center,end:redDot2.center)
...
μλμ κ°μ΄ μ μΌλ‘ λ κ°μ μ μ΄ μ΄μ΄μ§λλ€.
4. λλκ·Έκ° λλλ©΄ μ κ³Ό μ μ¬λΌμ§κ² νκΈ°
λλκ·Έκ° λλλ μκ°μλ μ κ³Ό μ μ΄ μ¬λΌμ ΈμΌκ² μ£ ?
λΉ¨κ°μ λ κ°μ μ μ μμ λ°κΏμ£Όλ λ©μλλ₯Ό ꡬνν΄μ€λλ€.
private func changeColor(_ color:UIColor) {
redDot1.backgroundColor = color
redDot2.backgroundColor = color
line.strokeColor = color.cgColor
}
dragSquare λ©μλμ λλκ·Έκ° λλ¬μ λ μμ ν¬λͺ μμΌλ‘ λ°κΏμ€λλ€.
@objc func dragSquare(_ sender: UIPanGestureRecognizer) {
...
}else if sender.state == .ended {
changeColor(.clear)
...
5. νΉμ κΈΈμ΄ μ΄μ λμ΄λλ©΄ λΉ¨κ°μ 1 μμ§μ΄κΈ°
μ μ μ‘μλΉκΈ°λ€κ° νΉμ κΈΈμ΄ μ΄μ λμ΄λλ©΄ λΉ¨κ°μ 1μ μμ§μ΄κ² λ§λ€μ΄ μ€κ±΄λ°μ.
λ¨Όμ λ κ°μ μ μ κΈΈμ΄λ₯Ό μμλ΄μΌκ² μ£ ?
μλμ κ°μ΄ λ κ°μ μ μ κΈΈμ΄λ₯Ό μμλ λλ€.
private func getTwoPointDistance(_ point1:CGPoint,_ point2:CGPoint) -> CGFloat {
let xDist:CGFloat = point2.x - point1.x
let yDist:CGFloat = point2.y - point1.y
return sqrt((xDist * xDist) + (yDist * yDist))
}
dragSquare λ©μλμμ λλκ·Έκ° λ³ν λμ μλμ κ°μ΄ λ κ°μ μ μ κΈΈμ΄λ₯Ό μμλΈ λ€
νΉμ κΈΈμ΄ μ΄μ λμ΄λλ©΄ λΉ¨κ°μ 2μ μμΉλ‘ λΉ¨κ°μ 1μ μμΉλ₯Ό λ³κ²½νκ² μ΅λλ€.
}else if sender.state == .changed {
redDot2.center = sender.location(in: view)
redDot2.backgroundColor = .red
addLine(start: redDot1.center,end:redDot2.center)
let distance:CGFloat = getTwoPointDistance(redDot1.center,redDot2.center)
if distance > 100 {
redDot1.center = redDot2.center
}
μλμ κ°μ΄ 보면 μμκ° μμ§μ΄λκ² λ§€μ° μ΄μνμ£ ?
UIView.animateλ₯Ό μ¬μ©νλ©΄ μμ§μμ ν¨μ¬ λΆλλ½κ² λ°κΏ μ μμ΅λλ€.
if distance > 100 {
UIView.animate(withDuration: 0.5) {
self.redDot1.center = self.redDot2.center
}
6. μ λκΉ νμ ν΄κ²°νκΈ°
νμ§λ§ μμμ μ λλ©μ΄μ μ μ μ©νμ λ λΆλλ¬μμ§κΈ΄ νμ§λ§ λΉ¨κ°μ 1μ΄ μ΄λ μ€μ μ κΉ μ μ΄ λκΈ°λ νμμ΄ λ°μνμ΅λλ€.
κ·Έ μ΄μ λ μ λλ©μ΄μ μ μ©νλ©΄ λ°λλ κ°μ λ°λΌ μ μ΄ μ°κ²°λμ§ μκΈ° λλ¬Έμ λλ€.
μ λλ©μ΄μ μ΄ λ°λλ κ°μ λ°λΌ μ μ μ°κ²°νλ €λ©΄ μ΄λ»κ² ν΄μΌν κΉμ?
λ°λ‘ CADisplayLinkλ₯Ό μ¬μ©ν΄μ£Όλ©΄ λ©λλ€.
CADisplayLinkλ νλ©΄μ΄ μ λ°μ΄νΈ λ λλ§λ€ νΈμΆλλ NSObjectμΈλ°μ.
μλμ κ°μ΄ μ μΈν΄μ£Όμκ³
var displayLink:CADisplayLink?
λμ€νλ μ΄λ§ν¬λ₯Ό μμ±ν΄ μ£Όλ λ©μλλ₯Ό ꡬνν΄μ€λλ€.
private func createDisplayLink() {
let displaylink = CADisplayLink(target: self,selector: #selector(displayLinkLoop))
displaylink.add(to: .current,forMode:.common)
displayLink = displaylink
}
κ·Έλ¦¬κ³ μ λλ©μ΄μ μ΄ μ€νλκΈ° μ§μ μ λμ€νλ μ΄ λ§ν¬λ₯Ό μμ±ν΄ μ€λλ€.
if distance > 100 {
createDisplayLink()
UIView.animate(withDuration: 0.5) {
self.redDot1.center = self.redDot2.center
}
κ·Έλ¦¬κ³ λμ€νλ μ΄λ§ν¬λ₯Ό μΆλ ₯ν΄λ³΄λ©΄
@objc func displayLinkLoop(displaylink: CADisplayLink) {
print(displaylink)
νλ©΄μ΄ μ λ°μ΄νΈ λ λλ§λ€ μλμ κ°μ΄ λμ€νλ μ΄ λ§ν¬κ° νΈμΆλ©λλ€.
μ΄κ²μ μ΄μ©ν΄μ μ λλ©μ΄μ μ μ¬μ©νμ§ μκ³ λΆλλ½κ² λΉ¨κ°μ 1μ μ΄λμν€κ² μ΅λλ€.
λΉ¨κ°μ 2μ μμΉλ₯Ό κΈ°μ΅ν goalμ λ§λ€μ΄μ€λλ€.
var goal = CGPoint()
μλμ κ°μ΄ λμ€νλ μ΄λ§ν¬λ₯Ό μμ±νκΈ° μ§μ μ goalμ μμΉλ₯Ό λΉ¨κ°μ 2μ μ€μμΌλ‘ μΈν ν©λλ€.
if distance > 100 {
goal = redDot2.center
createDisplayLink()
}
κ·Έλ¦¬κ³ λμ€νλ μ΄ λ§ν¬λ₯Ό νΈμΆνλ λ©μλμ μλμ κ°μ΄ goalμ μμΉκ° λ λκΉμ§ μ μΌλ‘ μ°κ²°ν΄μ€λλ€.
@objc func displayLinkLoop(displaylink: CADisplayLink) {
if round(redDot1.center.x) == round(goal.x) && round(redDot1.center.y) == round(goal.y){
displaylink.invalidate()
line.strokeColor = UIColor.clear.cgColor
return
}
if round(redDot1.center.x) != round(goal.x) {
redDot1.center.x += redDot1.center.x < goal.x ? 1: -1
}
if round(redDot1.center.y) != round(goal.y) {
redDot1.center.y += redDot1.center.y < goal.y ? 1 : -1
}
addLine(start: redDot1.center,end: redDot2.center)
}
}
κ·ΈλΌ μλμ κ°μ΄ μ λλ©μ΄μ μ μ΄μ©νμ§ μκ³ λΉ¨κ° μ 1μ μμΉλ₯Ό λΆλλ½κ² μ΄λμν¬ μ μμ΅λλ€.
7. λΉ¨κ°μ 1μ μμΉμ λ°λΌ μμ μμ§μ΄κΈ°
μ΄μ λΉ¨κ°μ 1μ μμΉμ λ°λΌμ μμλ₯Ό μμ§μ¬λ³΄λ κ²μ ꡬννκ² μ΅λλ€.
λΉ¨κ°μ 1μ μμΉλ 맨 μ²μ λλκ·Έκ° μμλ κ³³μ μμΉμ£ ?
κ³ λ‘ λΉ¨κ°μ 1μ μ€μκ³Ό μμμ μ€μμ μ°¨μ΄λ₯Ό κ³μ°ν΄μΌ ν©λλ€.
λΉ¨κ°μ 1κ³Ό μμμ μ°¨μ΄λ₯Ό κΈ°λ‘ν gapμ λ§λ€μ΄μ€λλ€.
private var gap:CGPoint = CGPoint()
κ·Έλ¦¬κ³ λλκ·Έκ° μμλ λ μμμ λΉ¨κ° μ μ μμΉλ₯Ό κ³μ°ν΄μ gapμ μ μ₯ν΄μ€λλ€.
if sender.state == .began {
let location = sender.location(in: view)
gap.x = square.center.x - location.x
gap.y = square.center.y - location.y
κ·Έ λ€μ λμ€νλ μ΄ λ§ν¬κ° νΈμΆλ λ μμμ μμΉλ₯Ό μ°¨μ΄λ§νΌ λν΄μ€ κ°μΌλ‘ μ§μ ν΄μ€λλ€.
@objc func displayLinkLoop(displaylink: CADisplayLink) {
if round(redDot1.center.x) == round(goal.x) && round(redDot1.center.y) == round(goal.y){
displaylink.invalidate()
line.strokeColor = UIColor.clear.cgColor
return
}
if round(redDot1.center.x) != round(goal.x) {
redDot1.center.x += redDot1.center.x < goal.x ? 1: -1
square.center.x = redDot1.center.x + gap.x
}
if round(redDot1.center.y) != round(goal.y) {
redDot1.center.y += redDot1.center.y < goal.y ? 1 : -1
square.center.y = redDot1.center.y + gap.y
}
addLine(start: redDot1.center,end: redDot2.center)
}
μλμ κ°μ΄ λΉ¨κ°μ 1μ μμΉμ λ°λΌ μμκ° μμ§μ΄κ² λ©λλ€.
8. λΉ¨κ°μ 1κ³Ό λΉ¨κ°μ 2μ 거리λκΈ°(μ΅μ λ)
λΉ¨κ°μ 1μ΄ λΉ¨κ°μ 2μ μμΉλ‘ κ°λ κ²μ΄ μλ κ·Έ κ·Όμ²λ‘ κ°κ²λ ꡬννκ³ μΆμλλ°μ.
(μ΄κ±΄ νμ λ λκ³ μνμ λ λ©λλ€.)
μ΄λ κ² μ€λͺ νλ©΄ μ΄ν΄κ° μλλ μΌμͺ½μ λΉ¨κ°μ 2μ μ’νκΉμ§ μ΄λνλ κ²μ΄κ³ μ€λ₯Έμͺ½μ λΉ¨κ°μ 2μ κ·Όμ²κΉμ§λ§ μ΄λνλ κ²μ λλ€.
μμμ μ€μμ κΈ°μ€μΌλ‘ x,yμ μμμ μμμ λ°λΌ λ²μλ₯Ό λλκ³ λΉ¨κ°μ 2μ μμΉμ λ°λΌ λΉ¨κ°μ 1μ μμΉλ₯Ό
ꡬν΄μ£Όμμ΅λλ€.
μλμ κ°μ΄ λ²μμ λ°λΌ ꡬν μ μμ΅λλ€.
func getDestinationPoint(p2:CGPoint) -> CGPoint {
if p2.x <= center.x {
//1
if p2.y <= center.y {
return CGPoint(x: p2.x+50, y: p2.y+50)
//3
}else {
return CGPoint(x: p2.x+50, y: p2.y-50)
}
}else {
//2
if p2.y <= center.y {
return CGPoint(x: p2.x-50, y: p2.y+50)
//4
}else {
return CGPoint(x: p2.x-50, y: p2.y-50)
}
}
}
goalμ μμΉλ₯Ό μλμ κ°μ΄ μ€μ ν΄μ€λλ€.
if distance > 100 {
goal = square.getDestinationPoint(p2: redDot2.center)
createDisplayLink()
}
9. μμ νμ νκΈ°
μ€μ λΉκ²¨ μμλ₯Ό μμ§μΌ λ λ°©ν₯μ λ°λΌ μμκ° μ½κ°μ© νμ λλ κ²μ λ³Ό μ μμ΅λλ€.
μ΄κ² λν μμ²λΌ λ²μλ₯Ό λλ λΉ¨κ°μ 2μ μμΉμ λ°λΌ μ΄λ λ°©ν₯μΈμ§ μμλΌ μ μμμ΅λλ€.
λ²μμ λ°λΌ μλμ κ°μ΄ νμ κ°μ μ§μ ν΄μ£Όμμ΅λλ€.
(5λ‘ μ§μ νμ§λ§ μνμλ νμ κ°μ λ£μΌμλ©΄ λ©λλ€.)
func getAngle(p2:CGPoint) -> CGFloat {
if p2.x <= center.x {
//1
if p2.y <= center.y {
return 5
//3
}else {
return -5
}
}else {
//2
if p2.y <= center.y {
return -5
//4
}else {
return 5
}
}
}
κ·Έλ¦¬κ³ λ·°λ₯Ό νμ ν μ μλ λ©μλλ₯Ό λ§λ€μ΄μ£Όμμ΅λλ€.
func rotate(degrees: CGFloat) {
let degreesToRadians: (CGFloat) -> CGFloat = { (degrees: CGFloat) in
return degrees / 180.0 * CGFloat.pi
}
self.transform = CGAffineTransform(rotationAngle: degreesToRadians(degrees))
}
μλμ κ°μ΄ λλκ·Έ κ°μ΄ λ°λ λ λΉ¨κ°μ 2μ μμΉμ λ°λΌ κ°λλ₯Ό κ΅¬ν΄ νμ νκ³
λλκ·Έκ° λλ λ νμ μ 0λλ‘ λ§λ€μ΄ μ μλ¦¬λ‘ λμμ€κ² ꡬννμμ΅λλ€.
@objc func dragSquare(_ sender: UIPanGestureRecognizer) {
if sender.state == .began {
let location = sender.location(in: view)
gap.x = square.center.x - location.x
gap.y = square.center.y - location.y
changeColor(.red)
redDot1.center = location
}else if sender.state == .changed {
redDot2.center = sender.location(in: view)
addLine(start: redDot1.center,end:redDot2.center)
let distance:CGFloat = getTwoPointDistance(redDot1.center,redDot2.center)
if distance > 100 {
goal = square.getDestinationPoint(p2: redDot2.center)
createDisplayLink()
let angle:CGFloat = square.getAngle(p2:redDot2.center)
rotateAndChangeColor(angle: angle, color: .systemRed)
}
}else if sender.state == .ended {
changeColor(.clear)
rotateAndChangeColor(angle: 0, color: .clear)
}
}
νμ κ³Ό μμ λ°κΏμ€ λ μ λλ©μ΄μ μ μ€μ λΆλλ½κ² λ§λ€μ΄ μ£Όμμ΅λλ€.
private func rotateAndChangeColor(angle:CGFloat,color:UIColor) {
UIView.animate(withDuration: 0.5) {
self.square.rotate(degrees:angle)
self.label.rotate(degrees:angle)
self.changeColor(color)
}
}
μλμ κ°μ΄ μ€μ λΉκΈ°λ λ°©ν₯μ λ°λΌ μμκ° νμ νλ κ²μ λ³Ό μ μμ΅λλ€.
μ€λμ μ΄λ κ² μΈν°λν°λΈ λ벨λ‘νΌλμ νν 리μΌμ ꡬνν΄λ³΄μλλ°μ.
보기μ μ λ§ κ°λ¨νλ€κ³ μκ°νλ κ²λ€μ΄ μ§μ ꡬννλ €κ³ νλ μκ°λ μ€λ κ±Έλ¦¬κ³ κ½€ 볡μ‘νμ΅λλ€.
μ΄ νν 리μΌμ ν΅ν΄μ λλ¦ λ°°μ΄ κ²λ λ§κ³ μμ λΆν° νκ³ μΆμλ ν¬λ¦¬μμ΄ν°λΈ μ½λ©μ΄λΌμ ꡬννκ³ λλ λΏλ―νλ€μ...γ γ