AI 사용기
SwiftUI 공유 시트가 비어 있을 때: item으로 공유 데이터 함께 넘기기
첫 공유가 빈 시트인 경우를 자체 예제로 대조하고, 선택한 문자열과 Copy 출력까지 확인하는 item 기반 구현을 제공한다.
2026년 10월 12일 조사·자체 예제 시험·작성한 글입니다.
공유 버튼 뒤 시트는 뜨지만 내용이 없는 iPhone SwiftUI 화면의 데이터 전달 경로를 확인하도록 돕는다.
표시 Bool과 공유 배열을 분리하는 대신 optional payload를 sheet의 데이터로 쓰고, 시트 내용과 실제 복사 결과를 함께 확인한다.
최소 예제에서 첫 A 공유와 다음 B 공유를 실행한 뒤, 자신의 공유 대상을 만드는 코드와 연결한다.
먼저 답하면
SwiftUI 공유 시트가 처음에는 비어 있고 나중에 다시 열면 내용이 나온다면, 표시용 Bool과 공유 데이터를 따로 관리하는 부분부터 확인한다. 이 글의 iPhone 텍스트 공유 예제는 optional SharePayload 하나를 .sheet(item:)에 연결하고, 클로저에 전달된 값으로 UIActivityViewController를 만든다. 지연 시간을 추가하는 방식은 사용하지 않는다.
새 iOS App 프로젝트를 SwiftUI로 만들고 ContentView.swift를 아래 코드로 교체한다. 생성된 App의 WindowGroup { ContentView() }는 유지하고 예제의 iOS 배포 대상은 17 이상으로 둔다. 이 코드는 iPhone 세로 화면용 최소 예제다.
import SwiftUI
import UIKit
struct SharePayload: Identifiable {
let id = UUID()
let text: String
}
struct ActivityView: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ controller: UIActivityViewController,
context: Context) {}
}
struct ContentView: View {
@State private var payload: SharePayload?
var body: some View {
VStack {
Button("Share A") {
payload = SharePayload(text: "Share A: 12")
}
Button("Share B") {
payload = SharePayload(text: "Share B: 34")
}
}
.sheet(item: $payload) { value in
ActivityView(items: [value.text])
}
}
}
Share A를 눌러 시트 상단의 Share A: 12를 확인하고 Copy를 선택한다. 닫힌 뒤 Share B를 눌러 이번에는 Share B: 34가 보이는지 확인하고 다시 복사한다. 복사한 문자열은 테스트용 메모·입력칸에 붙여넣어 대조할 수 있다. 시트가 보인다는 사실만으로 성공으로 판단하지 않는다. 선택한 값이 시트와 복사 결과에 모두 일치해야 한다. Copy는 클립보드를 바꾸므로 필요한 기존 내용은 먼저 보존한다.
ShareProbe.zip 받기에는 Xcode 프로젝트·자체 소스·UI 시험이 들어 있다. ZIP 앱의 Read copied text 버튼은 복사한 문자열을 바로 읽어 표시한다. A 뒤에는 Copied: Share A: 12, B 뒤에는 Copied: Share B: 34가 성공 기준이다. 본문 최소 코드에는 이 진단 버튼과 비교 분기가 없다. 다운로드 예제로 시험하려면 압축을 풀고 ShareProbe 폴더에서 Xcode로 프로젝트를 열어 iPhone 시뮬레이터를 선택·실행한다. 외부 패키지·XcodeGen·Apple 계정은 필요 없다.
표시 여부와 데이터를 하나의 상태로 묶기
Apple의 sheet(item:) 문서에서 item은 optional 데이터 원천이다. 값이 non-nil이면 그 값이 content 클로저로 전달된다. 따라서 payload = SharePayload(...)로 표시할 값과 시트의 입력을 함께 정하고, 클로저 매개변수 value.text를 UIKit 쪽에 넘긴다.
비교 대상으로 만든 코드는 별도 배열과 Bool을 사용했다.
@State private var items: [Any] = []
@State private var showShare = false
// Button의 action
items = ["Share A: 12"]
showShare = true
// 그 버튼을 담은 뷰의 modifier
.sheet(isPresented: $showShare) {
ActivityView(items: items)
}
이 조각은 문제 구조를 설명하는 비교용이며 위의 완전한 수정 코드에 추가하지 않는다. 자체 대조군에서는 첫 시트가 나타났지만 텍스트 캡션과 공유 동작 셀이 없었다. item 기반 화면에서는 첫 A의 캡션과 Copy 결과가 일치했다. 이 차이만으로 SwiftUI의 내부 평가 순서나 모든 Boolean sheet의 결함을 확정할 수는 없다. 데이터 없이 열 수 있는 상태와 실제 공유 데이터가 따로 있는 구조를 이 예제에서 줄인 것이다.
2022년 Swift Forums 질문에도 Xcode 13·iOS 15의 Boolean sheet가 첫 표시에서 초기 문자열을 보여 주는 사례가 있다. 이는 공유 컨트롤러가 아닌 일반 텍스트 시트이며 질문자의 과거 환경이다. 오늘의 모든 iOS 버전에서 같은 장애가 난다는 근거로 쓰지 않는다.
시트가 있다는 시험으로 놓치는 것
2026년 10월 12일 Xcode 27.0(27A266a), iOS 26.5, iPhone SE 3세대 전용 시뮬레이터의 세로 화면에서 자체 앱을 시험했다. 자동 UI 시험 3개가 실패 없이 끝났다. 시스템 메뉴는 일부 한국어로 보였고 이 환경의 복사 셀 이름은 Copy였다.
| 비교 | 실제 결과 |
|---|---|
| Bool + 별도 배열의 첫 A 공유 | ActivityListView 존재 확인은 통과했지만 내부 셀 0개, A 캡션 없음 |
| item 값은 있지만 UIKit 입력을 빈 배열로 바꾼 대조군 | 시트는 존재하나 내부 셀 0개, A 캡션과 Copy 없음 |
| item 기반 첫 A → Copy → Read copied text | 내부 셀이 있고 A 캡션 및 Copied: Share A: 12 일치 |
| 닫힌 뒤 B → Copy → Read copied text | B 캡션 및 Copied: Share B: 34 일치 |

첫 A 공유 대조군. 닫기 버튼이 있어도 선택한 문자열과 공유 동작 목록은 보이지 않는다.

수정 예제의 첫 공유. 상단의 Share A: 12와 아래 Copy를 함께 확인했다.
초기 계층 조사에서 수정 시트의 내부 셀은 4개였다. 이것을 고정 합격 숫자로 쓰지 않는다. 설치한 앱·공유 자료 종류·OS에 따라 동작 목록이 달라질 수 있다. 시험에서는 시트 내부 셀이 양수인지, 기대한 캡션이 있는지, Copy가 실제로 눌리는지, 복사된 값이 맞는지를 함께 확인했다. 화면 전체의 app.cells를 세면 뒤에 깔린 목록 셀까지 섞일 수 있으므로 이 환경에서는 ActivityListView 내부로 쿼리를 좁혔다. 이 시스템 접근성 식별자는 공개 SwiftUI API의 고정 보장이 아니므로 다른 OS에서는 실제 계층을 다시 확인해야 한다.
빈 배열 인자를 넣고 정상 결과를 기대하는 동일한 긍정 시험도 따로 실행했다. 셀 양수 검사와 캡션 검사가 실패했다. 빈 대조군도 통과하는 “시트 존재만” 검사가 정상 출력까지 보장하지 못함을 확인한 것이다.

B를 복사한 뒤 앱의 진단 버튼으로 읽은 값. 외부 앱에 전송됐다는 증거는 아니다.
ZIP의 자동 시험은 압축을 푼 ShareProbe 폴더에서 아래처럼 실행한다. YOUR_SIMULATOR_UDID는 xcrun simctl list devices available에 나온 사용 가능한 자기 iPhone 시뮬레이터 UUID로 바꾼다.
xcodebuild -project ShareProbe.xcodeproj -scheme ShareProbe \
-destination 'platform=iOS Simulator,id=YOUR_SIMULATOR_UDID' \
-derivedDataPath DerivedData -parallel-testing-enabled NO \
CODE_SIGNING_ALLOWED=NO test
다운로드 앱의 --baseline은 Bool 비교, --empty는 빈 배열 대조 인자다. Xcode의 Scheme → Run → Arguments에 추가하면 그 화면을 직접 볼 수 있다. 정상 동작을 확인할 때는 둘 다 넣지 않는다. UI 시험의 Copy 이름·시스템 식별자가 다른 환경에서는 코드가 빌드됐다는 것과 별개로 시험 쿼리도 점검한다.
적용 뒤에도 비어 있으면
- 버튼에서 최종 공유 문자열이 실제로 만들어지는지 확인한다. 비동기 작업이라면 작업이 성공해 데이터가 준비된 뒤 payload에 넣는다. 임의의 시간 지연은 성공 조건을 대신하지 못한다.
.sheet(item:)의 클로저 매개변수를 UIKit 입력으로 쓰는지 확인한다. 여전히 다른 오래된 배열을 읽거나 빈 배열을 전달하면 item이 있어도 공유 내용은 비어 있을 수 있다.- 첫 공유뿐 아니라 닫은 뒤 다른 선택값으로 다시 공유한다. A가 B에서도 남으면 전달한 값과 컨트롤러를 새로 만드는 경로를 확인한다.
- 시트는 채워졌는데 원하는 공유 앱이 없다면 자료 종류와 해당 기기의 앱·공유 동작을 확인한다. 셀 하나가 보인다는 것으로 특정 앱 전달 성공까지 판단하지 않는다.
이 래퍼의 updateUIViewController는 비어 있다. 열려 있는 공유 컨트롤러의 입력을 계속 갱신하는 예제가 아니다. 이번 시험은 Copy로 닫힌 뒤 새 payload로 B 시트를 여는 흐름이다. 공유 중 데이터를 변경하는 앱에는 별도 설계·시험이 필요하다.
Apple UIActivityViewController 문서는 앱이 공유 데이터와 표시·닫기를 구성해야 하며 iPad에서는 popover로 표시해야 한다고 구분한다. iPad·실기기·가로 화면·이미지/파일 공유·외부 앱 전달은 시험하지 않았다. 위 iPhone sheet 코드를 iPad에 그대로 옮겼을 때의 올바른 표시를 보장하지 않는다. iPad에서는 실제 표시 경로와 popover 조건을 따로 확인하고 시험한다. 이 글은 특정 sourceView 설정 한 줄이나 item 전환만으로 모든 빈 공유 시트를 해결한다고 약속하지 않는다.
AI가 공식 자료 조사, 자체 비교 예제 제작·시뮬레이터 UI 시험, 초안 작성과 별도 자동 검토를 수행했습니다. 기존 사용자 앱의 장애나 실제 사람 사용시험을 재현했다고 서술하지 않습니다.