먹고 기도하고 코딩하라

[SwiftUI] iOS 위젯에서 로컬 파일을 가져오기 본문

앱/Swift

[SwiftUI] iOS 위젯에서 로컬 파일을 가져오기

사과먹는사람 2024. 10. 25. 19:46
728x90
728x90

 

이번에 회사 앱 위젯 개선 작업을 했다. 위젯 작업은 원래 다른 분께서 하시던 작업이었으나, 그 분께는 다른 메인 작업이 있어 작업을 가져왔다.

WidgetKit과 SwiftUI로 레이아웃을 만드는 방법만 안다면 위젯은 그렇게 어려운 작업은 아니다. 다만 이번에 오프라인 모드에서 이미지를 가져오는 작업이 조금 까다로웠어서 포스팅한다.

 

 

문제

  • 오프라인 모드에서는 기기에 저장된 이미지 path를 불러와서 썸네일을 보여줘야 한다.
  • 그런데, path가 정확하고(documents) 이미지도 실재하는데 위젯에 표현이 안 된다.

원인

  • WidgetKit은 경량화 app extension이다. WidgetKit에서는 로컬에 저장된 파일들을 마음대로 접근할 수 없다.
  • 그러니까 실제로 Documents 디렉터리에 저장이 돼 있어도 읽을 수가 없는 것이다.

해결

  • App Group을 사용해 위젯과 공유해야 하는 파일들을 Documents 디렉터리에서 App Group으로 이동 혹은 복사한 다음 사용한다.

 

App Group은 같은 팀이 개발한 여러 앱을 1개 이상의 공유 컨테이너로 접근할 수 있게 해준다. App Groups는 샌드박스 앱 간 커뮤니케이션도 가능하게 해주는 등 앱 간 데이터 공유가 필요할 때 유용하게 사용할 수 있다.

일단 App Group을 원래 사용하지 않았다면, 프로젝트의 capabilities에서 App Groups를 체크한 다음 애플 개발자 사이트에서 추가해서 사용할 수 있다. 문서에서 방법을 안내하고 있으므로, 더 자세히는 문서를 참고하면 좋겠다.

 

Configuring App Groups | Apple Developer Documentation

Enable communication and data sharing between multiple installed apps created by the same developer.

developer.apple.com

 

다시 말하지만 이미 Documents 디렉터리에 저장된 걸 그대로 위젯에서 쓸 수는 없다.

하지만 위젯에서 정 필요한 정보라면, App Group의 공유 컨테이너 주소로 파일들을 이동/복사하고 공유 컨테이너에서 파일들을 사용하면 된다. (복사할 경우에는 원본 파일의 변경, 삭제 시 싱크를 맞춰줘야 할 것이다)

 

좋은 조수 챗지피티에게 코드를 부탁했다.

import Foundation

// Function to get the Documents directory URL
func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

// Function to get the App Group shared directory URL
func getSharedContainerURL() -> URL? {
    // 여기에선 지정한 group identifier를 넣으면 된다.
    return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourapp.widget")
}

// Function to move image from Documents to App Group shared directory
func moveImageToSharedContainer(fileName: String) {
    // Get the file URL in the Documents directory
    let documentsURL = getDocumentsDirectory().appendingPathComponent(fileName)
    
    // Get the file URL in the App Group shared container
    guard let sharedContainerURL = getSharedContainerURL() else {
        print("Failed to get shared container URL")
        return
    }
    let destinationURL = sharedContainerURL.appendingPathComponent(fileName)
    
    do {
        // Check if the file exists in the Documents directory
        if FileManager.default.fileExists(atPath: documentsURL.path) {
            // Move the file to the App Group shared directory
            try FileManager.default.moveItem(at: documentsURL, to: destinationURL)
            print("Successfully moved image to shared container: \\(destinationURL.path)")
        } else {
            print("Image not found in Documents directory: \\(documentsURL.path)")
        }
    } catch {
        // Handle any errors during the move operation
        print("Error moving image to shared container: \\(error.localizedDescription)")
    }
}

내 경우, 어떤 이미지 파일을 위젯에서 쓰게 될지 모르므로 이미지 파일들을 전부 복사해놔야 했다.

따라서 실제로는 이런 코드를 사용해 복사하게 된다. (지금은 모든 파일을 복사해두는 것이 비효율적이라는 리뷰를 받아 다른 방식으로 변경했지만, 복사하는 코드는 남겨둔다.)

func copyImageFileToSharedContainer() {
    guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { 
    return
    }
    do {
    var files: [String]? = try FileManager.default.contentsOfDirectory(atPath: docURL?.path ?? "")
        // 이미지인지 아닌지 판별할 수 있는 방법은 더 지혜로운 게 있을 것이다.
        files = files?.filter { $0.hasSuffix("jpg") || $0.hasSuffix("png") }
        files?.forEach { name in
        moveToSharedContainer(fileName: name)
        }
    } catch {
        Log(error.localizedDescription)
    }
}

위젯에서 이미지를 불러와서 사용할 때는, 인터넷상 이미지를 가져오는 것과 동일한 방법으로 가져올 수 있다.

// loadImage(imagePath: url.path ?? "")

func loadImage(imagePath: String) -> Image {
    guard let url = URL(string: urlString), !urlString.isEmpty else { return self }
		
    if let data = try? Data(contentsOf: url) {
        return Image(uiImage: UIImage(data: data) ?? UIImage())
    }
    return self
}

 

 

 

728x90
반응형
Comments