가장 중요한 오늘의 시연 영상부터 보고 오자!!!
사실 지금 진행하는 프로젝트에서 스크롤에 따른 레이아웃의 변화가 있어야 했다.
하지만 아직 UI가 완벽히 만들어 지지 않았기 때문에 혼자 공부하는 시간동안 위와 같은 행동을 만들 수 있는 프로토콜을 만들어서 나중에 재사용하기로 하였다.
검색을 했을때 이렇다할 해결법이 없었는데...
이 것을 보고 하.. 직접만들어야겠구나 생각했다.
protocol EasyToAnimation {
var targetHeight: Double { get set }
var viewList: [UIView] { get set }
var constranitList: [(UIView, Double) ->()] { get set }
func move(contentOffset y: Double)
func changeAlpha(_ y: Double) -> Double
func aTob(start a: Double, end b: Double, _ y: Double) -> Double
}
별 것없다...
targetHeight : 스크롤로 이미지를 재배치시키는 최대 스크롤 값
나는 노란 뷰를 다 덮을 때까지 레이아웃을 재배치 시키기 때문에 노란색뷰의 높이로 설정
viewList : 레이아웃을 재배치시킬 뷰들을 담을 list
constranitList : 해당 뷰를 어떤 레이아웃을 적용할지에 대한 클로져 배열
func move : contentOffset을 받아서 레이아웃 클로져를 실행시키는 함수
func changAlpha : 값에 따라 해당 뷰의 alpha 값을 1 ~ 0.5로 만드는 함수
func aTob : 시작 레이아웃 값부터 끝 레이아웃 값으로 변경시키는 함수
extension EasyToAnimation{
func move(contentOffset y: Double) {
let percentY = min ( 1, max ( y / self.targetHeight , 0))
self.viewList.enumerated().forEach { idx, element in
self.constranitList[idx](element, percentY)
}
}
func changeAlpha(_ y: Double) -> Double{
if (y == 1 || y == 0) { return 1 }
else { return y > 0.5 ? y : 1 - y }
}
func aTob(start a: Double, end b: Double, _ y: Double) -> Double {
if (y == 0) { return a }
if (y == 1) { return b }
if (a > b){
return a - ((a - b) * y )
}
else {
return a + ((b - a) * y )
}
}
}
먼저 이실직고하자면 aTob는 레이아웃끼리 의존성이 있을때 잘 돌아가지 않습니다 추후에 업데이트하겠습니다.
func move(contentOffset y: Double) {
let percentY = min ( 1, max ( y / self.targetHeight , 0))
self.viewList.enumerated().forEach { idx, element in
self.constranitList[idx](element, percentY)
}
}
우선 targetHeight으로 부터 얼마큼 즉 몇퍼센트 진행됬는지 0 ~ 1까지 나태내주는 percentY를 만들어주고
viewList에서 재배치할 view를 가져와서 해당 constraintList를 실행합니다.
이제 와서 생각하는건데 그냥 튜플로 한번에 담는게 오류가 적을 것 같다.
이번 경우에는 같은 인덱스에 같은 레이아웃 클로져가 들어있어서 꼭 인덱스를 맞춰야하는 단점이 있다.
func changeAlpha(_ y: Double) -> Double{
if (y == 1 || y == 0) { return 1 }
else { return y > 0.5 ? y : 1 - y }
}
이건 constraint 클로져에서 사용하는 함수라서 y라는 0 ~ 1까지의 값을 0.5 ~ 1로 뱉어준다.
func aTob(start a: Double, end b: Double, _ y: Double) -> Double {
if (y == 0) { return a }
if (y == 1) { return b }
if (a > b){
return a - ((a - b) * y )
}
else {
return a + ((b - a) * y )
}
}
이 함수는 시작 값부터 끝값을 주면 contentOffset.y의 퍼세트 값에 따라 레이아웃에 필요한 숫자를 뱉어주는 함수이다.
쨋든 이 프로젝트는 나중에 재활용할려고 쓰는 거기 때문에 사용법을 적어 놓는게 좋을 것같다.
private func updataLayout(){
viewList.append(mapView)
constranitList.append({ view, y in
view.snp.updateConstraints{
$0.height.equalTo(self.aTob(start: 400, end: 0, y))
}
})
viewList.append(imageView)
constranitList.append({ view, y in
view.alpha = self.changeAlpha(y)
view.snp.updateConstraints{
$0.top.leading.equalToSuperview().offset(self.aTob(start: 30, end: 0, y ))
$0.width.equalTo( self.aTob(start: 100, end: self.view.frame.width, y) )
$0.height.equalTo( self.aTob(start: 100, end: 300, y) )
}
})
}
우선 나는 mapview (노란색)과 imageView (회색)만 두개 보겠다.
mapview는 스크롤에 따라 높이를 줄여주었다.
왜냐하면 mapview는 그 높이에 따라 중앙에 내가 표시한 위치를 표시하는데 스크롤시 높이를 줄여주지 않으면 내가 찍은 포인트가 먼저 스크롤되어 위로 올라가버린는 상황이 발생한다.
contentView.addSubview(mapView) mapView.snp.makeConstraints{ $0.top.equalTo(view.safeAreaLayoutGuide) $0.centerX.equalToSuperview() $0.width.equalTo(scrollView.snp.width) $0.height.equalTo(400) } contentView.addSubview(infoView) infoView.snp.makeConstraints{ $0.centerX.equalToSuperview() $0.top.equalTo(scrollView.snp.top).inset(self.mapView.frame.height) $0.width.equalToSuperview() $0.height.equalTo(1000) }
그래서 위와같이 mapview를 view상단에 고정하고, 아래에 파란 뷰를 그 노란색의 view의 높이만큼 스크롤 뷰에서 inset을 주었고,
scrollview에 bounce를 false로 해서 문제를 해결했다.
사용법으로 돌아가면 우선 재배치할 뷰를 viewListappend한다.
viewList.append(mapView)
그 후 재배치시 변할 레이아웃을 작성하여
constraintList에 append한다.
constranitList.append({ view, y in
view.snp.updateConstraints{
$0.height.equalTo(self.aTob(start: 400, end: 0, y))
}
})
여기서 인자 view는 재배치할 view, y는 진행된 상황 percentY이다.
이때 변화시킬 부분을 적고 aTob를 활용하여 시작 점과 끝점을 적어주면 자연스래 바뀌게 된다.
또 생각이 드는건 y는 자주쓰는데 그냥 저장 타입 프로퍼티로 해서 사용하는 것도 좋았을 것 같다는 생각이 든다.
하나만 더 보자면
viewList.append(imageView)
constranitList.append({ view, y in
view.alpha = self.changeAlpha(y)
view.snp.updateConstraints{
$0.top.leading.equalToSuperview().offset(self.aTob(start: 30, end: 0, y ))
$0.width.equalTo( self.aTob(start: 100, end: self.view.frame.width, y) )
$0.height.equalTo( self.aTob(start: 100, end: 300, y) )
}
})
다른점은 적용한게 많다는 점과
changeAlpha가 있다는 것이다.
changeAlpha는 진행도와 맞게 alpha 값을 바꿔주는 함수이다.
블로그를 쓰는 것을 참 좋은 것같다.
내 코드를 설명할때, 내가 잘못했던 부분이나 더 좋았을거 같은 부분이 떠올라서이다.
그리고 언젠가 지금 만들고 있는 FastDevelope 시리즈를 라이브러리화할 생각이기에
이번 코드에서 고려하지않은 참조문제나, aTob가 다른 뷰와 레이아웃적인 의존성을 가질 때 잘 돌아가지 않는 점을 고쳐야할것 같다.