MemeTextField.swift
위쪽과 아래쪽에 있는 Text컴포넌트의 UI를 꾸며주는 코드이다.
import SwiftUI
struct MemeTextField: View {
@Binding var text: String
var body: some View {
TextField(text, text: $text)
.multilineTextAlignment(.center)
.lineLimit(nil)
.foregroundColor(.black)
.font(Font.system(size: 25, weight: .bold))
.textCase(.uppercase)
}
}
@Binding 어노테이션을 사용하여 text 변수를 사용하였는데 이렇게 지정한 이유는 여러개의 뷰에서 동시에 State 값을 참조가 가능하기 때문이다. 하나의 View에서만 참조를 할 때에는 State 어노테이션을 사용하고 여러개의 View에서 참조할 때에는 Binding 어노테이션을 사용한다.
ImageLayer.swift
import SwiftUI
struct ImageLayer: View {
@Binding var imageData: Data?
var body: some View {
NSUIImage.image(fromData: imageData ?? Data())
.resizable()
.aspectRatio(contentMode: .fit)
}
}
NSUIImage는 macOS에서는 NSImage로 데이터를 가져오고 iOS에서는 UIImage로 데이터를 가져올 수 있게 도와주는 컴포넌트이다. 이것은 iOS 폴더와 macOS 폴더에서 직접 만든것으로 여기서는 그냥 Image 컴포넌트랑 같다고 생각하면 된다. 이미지를 선택하지 않을 수도 있으므로 imageData를 옵셔널로 선언을 했다.
TextLayer.swift
import SwiftUI
struct TextLayer<ImageContent: View>: View {
@Binding var meme: Meme
let imageContent: () -> ImageContent
var body: some View {
ZStack(alignment: .bottom) {
ZStack(alignment: .top) {
imageContent()
MemeTextField(text: $meme.topText)
}
MemeTextField(text: $meme.bottomText)
}
}
}
ImageContent라는 View Generic을 가지고 있는 TextLayer 컴포넌트이다. 여기는 이미지 데이터가 들어가는 View 컴포넌트이다. ZStack안에 앞서 만든 MemeTextField 2개와 ImageContent를 넣음으로써 뼈대가 완성되었다.
IOS
macOS와 iOS에서 같은 이름의 파일이지만 소스코드가 다를경우 _OS 명을 이름뒤에 붙이면 알아서 구분을 해준다.
NSUIImage_iOS.swift
import UIKit
import SwiftUI
public typealias NSUIImage = UIImage
extension NSUIImage {
var data: Data? {
return self.pngData()
}
static func image(fromData data: Data) -> Image {
return Image(uiImage: UIImage(data: data) ?? UIImage())
}
}
우선 NSUIImage를 typealias 키워드를 통해 UIImage와 같도록 바꾸어준다. 그리고 extension을 통해 data를 pngData()로 바꾸어주고 image 함수를 통해 SwiftUI의 Image 컴포넌트안에 들어갈 데이터를 UIImage(data: data)로 바꾸어서 변환해준다.
UIImagePicker.swift
import SwiftUI
struct UIImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: UIImagePicker
init(_ parent: UIImagePicker) {
self.parent = parent
}
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
if let uiImage = info[.originalImage] as? UIImage {
parent.image = uiImage
}
parent.presentationMode.wrappedValue.dismiss()
}
}
@Environment(\.presentationMode) var presentationMode
@Binding var image: UIImage?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(
context: UIViewControllerRepresentableContext<UIImagePicker>
) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(
_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<UIImagePicker>
) {
}
}
여기 코드는 사진파일을 받기 위한 UIImagePicker을 설정하는 부분이다. 이미지를 선택할 때 사진 App에 들어가서 이미지를 가져올 때 사용한다. UIImagePicker 부분은 나중에 UIKit 프레임워크 카테고리를 만들어서 자세하게 다루어볼 예정이다. 여기서는 Info.plist 파일에 사진 파일에 접근할 수 있는 권한을 설정해야된다는 것만 말하고 넘어갈 것이다. 그리고 새로운 어노테이션이 보이는데 바로 @Environment이다.
이전에 있었던 State와 Binding은 View에서 사용하는 변수를 공유하는 것이라면 Environment는 View의 환경설정을 공유하는 것이다. 여기서는 SwiftUI의 뷰와 UIImagePicker의 presentationMode를 공유하고 있다.
MemeEditor_iOS.swift
import SwiftUI
struct MemeEditor: View {
@Binding var meme: Meme
@State var showingImagePicker = false
@State private var inputImage: NSUIImage?
func loadImage() {
guard let inputImage = inputImage else { return }
meme.imageData = inputImage.data
}
var body: some View {
TextLayer(meme: $meme) {
Button {
showingImagePicker = true
} label: {
if meme.imageData != nil {
ImageLayer(imageData: $meme.imageData)
} else {
Text("Add Image")
.foregroundColor(.white)
.padding()
.background(Color("rw-green"))
.cornerRadius(30)
.padding(.vertical, 50)
}
}
}
//isPresented 값이 true일 때 실행
.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
UIImagePicker(image: self.$inputImage)
}
}
}
여기서는 showingImagePicker 변수를 통해 UIImagePicker를 불러와서 사진을 업로드할 수 있게 해놓았다. 버튼을 클릭하면 showingImagePicker가 true로 바뀌면서 UIImagePicker를 부른다. 사진을 선택하면 아까 @Environment으로 바인딩한 presentationMode가 dismiss가 되면서 UIImagePicker가 종료되고 .sheet의 onDismiss 파라미터에 있는 loadImage 함수가 실행된다.
Uploaded by Notion2Tistory v1.1.0