티스토리 뷰

첫번째 view는 sticky header를 만들어보자. AppStore에서 많이 볼 수 있고, 배너가 있는 view에서 주로 사용된다.

 

 

 

설계

상단에 배너이미지를 넣고 스크롤 할 수 있도록 아래에 적당한 내용을 추가한다. 배너는 이미지와 텍스트로 구성하며 아래로 스크롤을 하면 이미지가 스케일되면서 윗변이 화면의 상단에 붙어있는듯한 효과를 준다.

 

배너이미지를 스크롤하면 상단에 붙어서 스크롤할 때 스케일이 자동으로 조절된다.

 

 

 

구현

var body: some View {

	ScrollView(.vertical) {
    
    	GeometryReader {g in
        
        	// content
        }
    }
    .edgeIgnoringSafeArea(.top) 
}

스크롤 기능이 있어야하기 때문에 ScrollView로 시작한다. 노치를 무시하기 위해서 상단 SafeArea를 무시하도록 설정한다. 이 안에 세로로 컨텐츠들을 넣으면 된다. 배너는 스크롤을 할 때마다 스케일을 조절할 수 있어야하는데 이 기능을 위해서 Geometry Reader라는 것을 사용한다. GeometryReader는 자신이 화면에서 어떤 위치에 어떤 크기로 그려지고있는지 등 관련 정보를 가지고있다.

 

화면에서 위치와 크기를 알 수 있다.

 

이 때 배너로 사용한 이미지의 정렬 때문에 한참 애를 먹었다. aspectRatioframe의 상관관계를 정확히 이해하지 못해서 발생한 문제였다. 이미지의 높이를 설정하면 자동으로 넓이도 화면의 넓이에 맞게 조절될 줄 알았는데, 넓이(width)를 명시하지않으면 이미지가 차지할 수 있는 영역만큼 자동으로 조절되고, 그렇게 넓어진 이미지가 GeometryReader 안에 들어가면서 화면을 벗어나게 되는것이다.

 

width값을 GeometryReader의 width로 명시해주니 해결되었다.

 

GeometryReader에 이미지를 추가하는 코드를 간단하게 적어봤다.

GeometryReader {g in 
	let yOffset = g.frame(in: .global).minY > 0 ? -g.frame(in: .global).minY : 0 
    
    Image("banner") 
    	.resizable() 
        .aspectRatio(contentMode: .fill) 
        .frame(width: g.size.width, height: g.size.height - yOffset) 
        .offset(y: yOffset) 
} 
.frame(height: UIScreen.main.bounds.height / 2) // screen size

지금까지 써왔던 View들과 다른점은 closure에 g in이라는 표현이 추가되었다는 점인데, 이건 생성자가 조금 달라서 그렇다. 지금까지 써왔던 다른 View들은 init에서 () -> Content라는 closure를 매개변수로 받는다. 그래서 따로 추가적인 명시 없이 {}만으로 Content를 구성할 수 있었다. 하지만 GeometryReader는 (GeometryProxy) -> Content라는 closure를 매개변수로 받기 때문에 이 프로시 변수를 명시해주어야 한다.

c++을 주로 해서그런가 이런 문법들이 잘 안읽힌다. 알고나면 코드도 간결하고 괜찮아보이는데, swift가 익숙하지 않은 상태에서 이 코드가 무슨일을 하는지 알아내는데 정말 오랜 시간이 걸렸다.

이미지만 추가하면 밋밋하니까 ZStack으로 타이틀 텍스트를 추가해보았다.

GeometryReader {g in 
	let yOffset = // skip... 
    
    ZStack(alignment: Alignment(horizontal: .leading, vertical: .bottom)) { 
    
    	Image("banner") 
        	// skip... 
            
        Text("Title") 
        	.font(.title) 
            .fontWeight(.heavy) 
            .offset(y: yOffset) 
            .padding([.leading, .bottom]) 
    } 
} .frame(height: UISCreen.main.bounds.height / 2)

ZStack은 Z축으로 view들을 쌓아서 화면에 보여주는데 좌축 하단에 정렬하도록 설정했다. 이미지 위에 텍스트를 추가하고 크기와 offset을 설정해준다.

 

ZStack은 추가하는 view를 순서대로 쌓아서 보여준다.

 

이제 하단에 스크롤을 할 수 있을정도로 컨텐츠들을 추가해주면 된다. 긴 글을 써도 좋고, 이미지를 넣어서 쉽게 양을 늘릴 수도 있다.

var body: some View { 

	ScrollView(.vertical) { 
    
    	GeometryReader {g in 
        	// skip... 
        } .frame(height: UIScreen.main.bounds.height / 2) 
        
        Image("dummy01") 
        
        Image("dummy02") 
        
        Text("content") 
    } .edgeIgnoringSafeArea(.top) 
}

 

 

 

Preview

배너 이미지가 화면 상단에 붙어서 스크롤될 때마다 크기와 스케일이 조절된다.

 

 

 

Navigation Node

이전에 Tutorial project를 만들면서 새롭게 추가하는 view들을 Navigation List에 추가하기로 했다. 상세 view는 다 만들었으니 NavigationList에서 보여질 view를 구성하자. 간단하게 배경에 들어갈 썸네일과 타이틀을 ZStack으로 구성한다. 위 내용은 아무데나 들어가도 상관없지만 StickyHeader.swift에 같이 작성하는게 가장 좋아보여서 StickyHeader 상단에 작성했다.

struct StickyHeaderNode: View {
	var body: some View {
		
		ZStack(.leading) {
			
			Image("banner")
				.resizable()
				.aspectRatio(.fill)
				.frame(height: 100)

			Text("Sticky Header")
				.font(.title)
				.padding()
		}
	}
}

그리고 이렇게 만든 Node를 MainView.swift에서 NavigationLink에 추가하면 된다.

NavigationView() {
	
	List() {
			
		NavigationLink(destination: StickyHeader()) {
			StickyHeaderNode()
		}
		.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
	}
}

이제 MainView를 실행해서 StickyHeader 리스트를 터치하면 위에서 만든 StickyHeaderView로 이동한다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함