[iOS] URL 미리보기뷰 커스텀 - LinkPresentation #2

Sehee·2024년 5월 7일

iOS 개발하기

목록 보기
2/16
post-thumbnail

지난 포스팅에서

LinkPresentation의 기본 사용법?에 대해 알아보았는데, 궁금하신 분들은 지난 포스팅 먼저 보고 오세요

지난 포스팅 : [iOS] URL 미리보기 뷰 - LinkPresentation #1


커스텀?

커스텀하려면 LPLinkView를 활용하지 않고, LPLinkMetadata를 활용해 직접 뷰를 만들어야했다
역시나 구글링하니까 나오길래 편하게 따라쳐봤다 (아래 링크 참고)

참고사이트
https://medium.com/@alaputska/creating-custom-link-preview-instead-of-lplinkview-with-swiftui-909512a4cb27

사실 iOS 개발 안한지 꽤 되어서, 전체 코드블럭이 아닌, 조각난 코드블럭들을 이어붙이는 작업에서 어려움을 꽤나 겪었다 (공부 안한 내 잘못)
아무튼 어찌저찌 성공은 했지만, 현재 코드는 매우 더러운 상태,,,

이 글을 보는 당신, 구조화는 알아서 하도록...

우선 필요한 라이브러리는 다음과 같다

import SwiftUI
import LinkPresentation
import UniformTypeIdentifiers

model을 선언해줄건데, model 안에 fetchmeta() 함수와 convertToImage() 함수 두 개가 함께 선언되어있다
fetchMetadata() : url을 가지고 LPLinkMetadata를 반환해주는 함수
convertToImage() : LPLinkMetadata가 가지고 있는 image 데이터를 화면에 그릴 수 있도록 UIImage 형태로 변환해주는 함수

final class PreviewViewModel: ObservableObject {
    
    @Published var image: UIImage?
    @Published var title: String?
    @Published var url: String?
    
    let previewURL: URL?
    
    init(_ url: String) {
        self.previewURL = URL(string: url)
        
        fetchMetadata()
    }
    
    private func fetchMetadata() {
        guard let previewURL else { return }
        let provider = LPMetadataProvider()
        
        Task {
            let metadata = try await provider.startFetchingMetadata(for: previewURL)
            
            image = try await convertToImage(metadata.imageProvider)
            title = metadata.title
            
            url = metadata.url?.host()
        }
    }
    
    private func convertToImage(_ imageProvider: NSItemProvider?) async throws -> UIImage? {
        var image: UIImage?
        
        if let imageProvider {
            let type = String(describing: UTType.image)
            
            if imageProvider.hasItemConformingToTypeIdentifier(type) {
                let item = try await imageProvider.loadItem(forTypeIdentifier: type)
                
                if item is UIImage {
                    image = item as? UIImage
                }
                
                if item is URL {
                    guard let url = item as? URL,
                          let data = try? Data(contentsOf: url) else { return nil }
                    
                    image = UIImage(data: data)
                }
                
                if item is Data {
                    guard let data = item as? Data else { return nil }
                    
                    image = UIImage(data: data)
                }
            }
        }
        
        return image
    }
}

그리고 커스텀뷰를 만들어준다
간단하게 리스트형태로 만들었다

참고로, 이 부분은 위에 참고한 사이트에서 코드 따라적은 후에, 디자인 좀더 깔끔하게 다듬었다 (대충 살짝 다르단 얘기)

struct URLPreviewView: View {
    
    let links: [StringLink] = [
        StringLink(id: UUID(), string: "url1"),
        StringLink(id: UUID(), string: "url2"),
        StringLink(id: UUID(), string: "url3"),
        StringLink(id: UUID(), string: "url4")
    ]
    
    var body: some View {
        VStack {
            List(links) { l in
                VStack {
                    
                    URLPreviewRow(
                        viewModel: PreviewViewModel(l.string))
                }
            }
        }
    }
}

struct URLPreviewRow: View {
    
    @ObservedObject var viewModel: PreviewViewModel
    
    var body: some View {
        
        HStack(spacing: 15) {
            if let image = viewModel.image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(maxWidth: 107, maxHeight: 107)
                    .clipped()
                    .cornerRadius(16)
            }
            
            VStack(alignment: .leading, spacing: 1, content: {
                if let title = viewModel.title {
                    Text(title)
                        .font(.body)
                        .foregroundColor(.black)
                        .multilineTextAlignment(.leading)
                }
                
                if let url = viewModel.url {
                    Text(url)
                        .font(.footnote)
                        .foregroundColor(.gray)
                        .multilineTextAlignment(.leading)
                }
            })
            .padding(.vertical, 10)
            .padding(.trailing, 20)
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        .frame(maxWidth: .infinity)
        .frame(height: 100, alignment: .leading)
    }
}

StringLink는 지난 포스팅에 있지만, 찾기 귀찮을테니까 한번 더 적어주겠다

struct StringLink: Identifiable {
    var id = UUID()
    var string: String
}

그러면 다음과 같은 화면이 출력된다
iOS_LinkPresentation_2_1

LPLinkMetaData의 속성

근데 이제 깃허브는 preview 이미지 말고 icon 이미지 가져오고 싶다는 생각이 들거다
LPLinkMetaData의 속성을 보면된다
apple_developer_docs_캡쳐본

애플 공식 문서를 보면, LPLinkMetaData에는 다음과 같은 속성들이 있다
(설명은 번역기 돌렸음)

속성명타입설명
urlURL?서버측 리디렉션을 고려하여 메타데이터를 반환한 URL
originalURLURL?메타데이터 요청의 원래 UR
(대충 처음 입력된 URL)
titleString?URL의 대표 제목
iconProviderNSItemProvider?URL의 대표 아이콘에 해당하는 데이터를 검색하는 객체
(ex: favicon, 공식 로고 등)
imageProviderNSItemProvider?URL에 대한 대표 이미지에 해당하는 데이터를 검색하는 객체
(ex: 블로그 썸네일 등)

image 대신 icon

iconProvider라는 속성, 누가봐도 매력적이게 생겼다
왠지 이걸 써야할것만 같다
imageProvider과 타입이 동일하니, 대충 convertToImage() 활용해서 써보자

  1. PreviewViewModel 속성에 icon 추가해주기
@Published var icon: UIImage?
  1. fetchMeta() 함수에서 icon도 image와 동일하게 코드 추가
private func fetchMetadata() {
	// 중략
    Task {
    	let metadata = try await provider.startFetchingMetadata(for: previewURL)
        image = try await convertToImage(metadata.imageProvider)
        
        // image와 동일하게 icon도 추가
        icon = try await convertToImage(metadata.iconProvider)

		// 중략
    }
}
  1. 커스텀뷰에서 image를 icon으로 변경하기
struct URLPreviewRow: View {
    
    @ObservedObject var viewModel: PreviewViewModel
    
    var body: some View {
        
        HStack(spacing: 15) {            
            // image -> icon으로 변경하기
            if let icon = viewModel.icon {
                Image(uiImage: icon)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(maxWidth: 107, maxHeight: 107)
                    .clipped()
                    .cornerRadius(16)
            }
        }
        // 생략
    }
}

그러면 다음과 같은 화면이 출력된다
iOS_LinkPresentation_2_2

마치며.

갈 길이 멀다...
그래도 오랜만에 iOS 개발 하니까 너무 재밌다😆

profile
디자인하는 개발자

0개의 댓글