티스토리 뷰

UILabel을 animate 시켜보자. ViewController 사이를 이동할 때, 한 ViewController 안에서 내용에 따라 layout이 달라져야 할 때 UIView.animate로 view를 이동시키는데, 그 중 UILabel은 없어서는 안되는 view이기 때문에 UILabel로 시작하는게 좋을 것 같다.

 

이런걸 한 번 만들어보자.

 

쉽게 생각해보자. UIView.animate는 UIView의 최종 상태를 정하는 closure를 통해 animation을 재생한다. 그렇다면 label의 font size를 늘려주면 되지 않을까?

 

UIView.animate(withDuration: 2.0, animations: {
	label.font = .systemFont(ofSize: 32)
})

 

모든 UIView property에 대해 UIView.animate가 자동으로 animation을 생성해 주면 좋겠지만 안타깝게도 UIView.animate는 CGFloat을 사용하는 일부 property만 animate 기능을 제공한다. 정리된 리스트는 찾지 못했지만 아마 단순 lerp연산으로 animation을 만들 수 있는 CGFloat property만 되는 것 같다.

아무튼 font property를 animate시키면 animate를 호출하는 시점에 font가 지정되고 animation이 전혀 되지 않는다.

 

 

 

CGAffineTransform

UIView가 가지고 있는 property 중에 transform 이라는 CGAffineTransform property가 있다. 이 struct는 6개의 CGFloat을 가지는 matrix 구조체다. 사실 3x3Matrix이기 때문에 9개의 값을 가져야 하지만 UI는 2D 좌표계만 사용하기 때문에 x, y 계산을 위해 필요한 6개만 저장하고 있다.

이 property는 animate 연산이 가능하다.

 

UIView.animate(withDuration: 2.0, animations: {
	label.transform = CGAffineTransform(scaleX: 2, y: 2)
})

 

scale을 크게 키워서 그릴 수도 있지만 이렇게 scale을 키울 경우 font크기는 그대로이기 때문에 font의 가장자리 픽셀이 뭉게져서 보일 수 있다. 그래서 animation 직전에 font size를 키우고, scale을 줄인 뒤에 다시 (1, 1) 크기로 scale하는 방법을 사용하면 비교적 깔끔하게 font를 그릴 수 있다.

 

// old font size is 16
label.font = .systemFont(ofSize: 32)
label.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)

UIView.animate(withDuration: 2.0, animations: {
	label.transform = CGAffineTransform(scaleX: 1, y: 1)
})

 

결과를 보기 전에 전체 코드 구조를 한 번 보자. label은 main view에 추가되어 있고, 화면의 중앙에 위치하도록 centerX, centerY constraint를 설정했다. label의 위치는 일부러 화면의 가운데로 위치하도록 했는데, 이유는 결과를 보면 알 수 있다.

 

아주 잘 되는 것 처럼 보인다.

 

심지어 constraint를 수정해서 화면 상단으로 이동하도록 만들면, 화면 상단으로도 이동하게 만들 수 있다.

 

위치 이동도 된다.

 

label이 화면 중앙에 위치하고 있어서 되는 것 처럼 보이지만 label의 위치를 중앙이 아니라 다른 곳으로 이동시켜서 해 보면 이상하게 scale되는 것을 볼 수 있다. 예를 들어 중앙 좌측으로 정렬된 label을 scale시켜보자.

 

label의 위치가 이상하게 변한다.

 

이건 UIView의 anchor가 기본적으로 (0.5, 0.5)에 위치하기 때문이다.

 

 

 

anchorPoint

UIKit는 화면에 view를 그리기 위해 UIView가 가지고 있는 여러 property들을 사용한다. 이 값으로 계산해서 그려지는 위치와 크기는 frame이라는 property에 정해진다.

따라서 anchorPoint의 위치를 바꾸면 scale이 anchorPoint를 기준으로 되지만 label이 또 이상한 위치에 그려진다. position은 바꾸지 않았기 때문에 해당 position에 anchorPoint를 두고 frame을 계산하면 원하는 위치와 어긋난다.

 

position도 같이 바꿔야 한다.

 

이렇게 scale을 시켜서 잘 되면 좋겠지만 frame은 constraint에 따라 또 달라지기 때문에 autoresizingMask를 사용하지 않고 constraint를 직접 추가했다면 또 다른 방법을 사용해야 한다.

 

 

 

transform

다시 transform으로 돌아왔다. position을 변경해서 안된다면 transform을 직접 수정하면 된다. transform은 위치, 크기, 회전 정보가 모두 담긴 matrix 이기 때문에 scale을 시키면서 동시에 위치를 알맞게 계산하면 원하는 결과를 만들 수 있다.

UIView를 확장해서 anchorPoint와 scale을 적용시킬 수 있는 새로운 함수를 추가했다.

 

extension UIView {
	func applyAnchorPoint(point: CGPoint) {
		layer.anchorPoint = point
		let xOffset = (point.x - 0.5) * bounds.width
		let yOffset = (point.y - 0.5) * bounds.height
		transform = CGAffineTransform(translationX: xOffset, y: yOffset)
	}

	func applyScale(scale: CGFloat) {
		let anchor = layer.anchorPoint
		let xOffset = 1 / scale * (anchor.x - 0.5) * bounds.width
		let yOffset = 1 / scale * (anchor.y - 0.5) * bounds.height
		transform = CGAffineTransform(scaleX: scale, y: scale).translatedBy(x: xOffset, y: yOffset)
	}
}

 

scale에 따라 위치를 조정하면서 그리기 때문에 anchorPoint를 항상 같은 위치로 고정시킬 수 있다. 주의해야 할 점은 anchorPoint와 constraintAnchor의 조합이 잘 맞아야 한다는 점인데, y축 center를 고정시켰다면 yAnchor 또한 0.5로 정해야지 scale이 잘 된다.

font size 말고 textColor도 바꾸고 싶은데 이건 transform으로 할 수가 없다. CATextLayer를 사용해서 animation을 시키면 되긴 한데, UILabel과 다르게 font 픽셀이 깨져보여서 마음에 들지 않는다. 더 연구를 해야겠다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함