[TIL]04.17

rbw·2022년 4월 17일
0

TIL

목록 보기
3/97

UIKit과 함께 사용하는 SwiftUI

SwiftUI 에서 UIView를 추가하기.

UIViewRepresentable protocl을 활용해야 합니다

UIViewRepresentable를 따르는 struct를 만들어서 SwiftUI에서 바로 사용하면 됩니다.

이 struct는 반드시 두 가지 메소드를 구현해야 합니다.

  • func makeUIView(context: Context) -> UIKit에서의 타입
  • SwiftUI에서 나타낼 뷰를 반환합니다.
  • func updateUIView(_ uiView: UIKit에서의 타입, context: Context)
  • SwiftUI에서 업데이트가 발생할 때 실행됩니다.
final class RedLabel: UILabel {
    override func awakeFromNib() {
        super.awakeFromNib()

        textColor = .red
    }
}

struct RepresentableRedLabel: UIViewRepresentable  {
    var text: String

    let redLabel = RedLabel()

    func makeUIView(context: Context) -> UILabel {
        redLabel
    }
    func updateUIView(_ uiView: UILabel, context: Context) {
        redLabel.text = text
    }
}

struct ContentView: View {
    var body: some View {
        RepresentableRedLabel(text: "내용")
    }
}
// UILabel의 RedLabel을 SwiftUI에서 사용하는 경우입니다.

SwiftUI에서 UIViewController를 추가하기

UIViewController를 SwiftUI에서 사용하기 위해서는 UIViewControllerRepresentable가 필수입니다.

UIViewControllerRepresentable를 구현하기 위해서는 반드시 두 가지 메소드를 구현해야합니다.

– func makeUIViewController(context: Context) -> some UIViewController
– func updateUIViewController(_ uiViewController: some UIViewController, context: Context)

struct ImagePickerController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIImagePickerController{
        UIImagePickerController()
    }
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        ImagePickerController()
    }
}

UIImgePickerController에서 선택한 UIImage를 Coordinator를 사용해서 SwiftUI에 표시하는 코드

struct ImagePickerController: UIViewControllerRepresentable {
    // SwiftUI 에서의 부모뷰의 @State property부터의 Binding
    @Binding var selectedImage: Image?
    @Binding var existSelectedImage: Bool
 
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePickerController = UIImagePickerController()
        imagePickerController.delegate = context.coordinator
 
        return imagePickerController
    }
 
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
 
    //  Cordinator 대입.
    func makeCoordinator() -> Coordinator {
        Coordinator(selectedImage: $selectedImage, existSelectedImage: $existSelectedImage)
    }
 
}
 
extension ImagePickerController {
    // Cordinator 만들기
    final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        @Binding var selectedImage: Image?
        @Binding var existSelectedImage: Bool

        // 밑줄을 사용해서 초기화 해야한다. 프로퍼티 래퍼임을 알려주기 위해서.
        init(selectedImage: Binding<Image?>, existSelectedImage: Binding<Bool>) {
            _selectedImage = selectedImage
            _existSelectedImage = existSelectedImage
        }
 
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard let selectedOriginalImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
 
            selectedImage = Image(uiImage: selectedOriginalImage)
            existSelectedImage = true
        }
    }
 
}
 
struct ContentView: View {
    @State private var selectedImage: Image?
    @State private var existSelectedImage = false
 
    var body: some View {
        ZStack {
            VStack {
                selectedImage?
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 100.0, height: 100.0)
                Button(action: didTapSelectedImageButton) {
                    Text("Select Image")
                }
            }
 
            if selectedImage == nil {
                ImagePickerController(
                    selectedImage: $selectedImage,
                    existSelectedImage: $existSelectedImage
                )
            }
        }
    }
 
    private func didTapSelectedImageButton() {
        selectedImage = nil
        existSelectedImage = false
    }
}

// @State property와 Binding으로 Coordinator에서 부모 뷰에 업데이트를 별도로 하지 않아도 간단하게 구현함

기존에 UIView 또는 UIViewController에서 SwiftUI View를 사용할 때에는 UIViewRepresentable을 따르는 Struct를 매번 따로 만들어 줘야할필요 없이 @Bindng할 property가 필요없는 경우에는 UIKit class에서 extension으로 구현이 가능하다.

final class Label: UILabel {}
 
//  Extension 으로 정의
extension Label: UIViewRepresentable {
    func makeUIView(context: Context) -> UILabel {
        backgroundColor = .gray
        text = "This is UIKit UILabel"
 
        return self
    }
 
    func updateUIView(_ uiView: UILabel, context: Context) {}
}
 
struct ContentView: View {
    var body: some View {
        VStack {
            Text("This is Swift UI Text")
            Label()
        }
    }
}

UIKit 에서 SwiftUI를 추가하기.

UIHostingController를 사용해야 합니다.

별도로 추가해야하는 method나 property는 없습니다.

UIHostingController(rootView: UIKit에서 표시할 SwiftUI): SwiftUI View를 초기화해서 넣어주기만 하면 끝

struct ContentView: View {
    var body: some View {
        Text("내용")
            .fontWeight(.bold)
    }
}
 
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
 
        
        let hostingController = UIHostingController(rootView: ContentView())
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.frame = view.bounds
 
        addChild(hostingController)
        view.addSubview(hostingController.view)
    }
}

UIHostingController의 rootView로 SwiftUI를 넣어주기만 하면 된다. addChild, addSubView는 지금까지 UIKit에서 UIViewController를 추가시킬 때와 같다.

struct ContentView: View {
    var body: some View {
        Text("펭 - 하!")
            .fontWeight(.bold)
    }
}
 
class ViewController: UIViewController {
 
    @IBAction private func didTapPresentButton(_ sender: Any) {
        let hostingController = UIHostingController(rootView: ContentView())
        present(hostingController, animated: true)
    }
 
}

SwiftUI 화면을 present 시킬 때도 SwiftUI 뷰를 UIHostingController로 감싸는 것 이외의 UIKit 코드는 지금까지 동일하게 사용하면 된다.


SwiftUI View를 다른 View 구조체에 변수로 전달하는 방법

@ViewBuilder를 이용하는 방법

struct ContainerView<Content: View>: View {
    @ViewBuilder var content: Content

    var body: some View {
        content
    }
}
// 뷰에 단순히 넣는것 뿐만 아니라, if-else, switch-case 블럭에서도 사용 가능하다.
struct SimpleView: View {
    var body: some View {
        ContainerView{
            Text("SimpleView Text")
        }
    }
}

struct IfElseView: View{
    var flag = true
    
    var body: some View {
        ContainerView{
            if flag {
                Text("True text")
            } else {
                Text("False text")
            }
        }
    }
}

struct SwitchCaseView: View {
    var condition = 1

    var body: some View {
        ContainerView {
            switch condition {
                case 1:
                    Text("one")
                default:
                    Text("Default")
            }
        }
    }
}

제네릭과 이니셜라이저를 사용한 방법

struct ParentView: View {
    var body: some View {
        NavigationView{

            VStack(spacing: 8){

                ChildView(destinationView: Text("View1"), title: "1st")
                ChildView(destinationView: Text("View2"), title: "2nd")
                Spacer()
            }
            .padding(.all)
            .navigationBarTitle("NavigationLinks")
        }
    }
}

struct ChildView<Content: View>: View {
    var destinationView: Content
    var title: String

    init(destinationView: Content,  title: String) {
        self.destinationView = destinationView
        self.title = title
    }

    var body: some View {
        NavigationLink(destination: destinationView){
            Text("This item opens the \(title) view").foregroundColor(Color.black)
        }
    }
}

SwiftUI | List (UITableView)

SwiftUI에서 UITableView는 List입니다.

List

UIKit에서 사용했던 UITableViewDelegate, UITableViewDataSource 없이 간단히 구현이 가능하다.

struct ContentView: View {
    var body: some View {
        List {
            Text("1")
            Text("2")
            Text("3")
        }
    }
}

따로 커스텀해서 항목을 추가하는것도 간단히 가능하다

struct CustomCell: View {
    var body: some View {
        HStack {
            Image(systemName: "tortoise.fill")
            Text("거북이")
        }
    }
}
 
struct ContentView: View {
    var body: some View {
        List {
            CustomCell() 
            Text("1")
            Text("2")
            Text("3")
        }
    }
}

List가 네비게이션뷰의 하위 뷰로 선언되어있고 Cell이 네비게이션 링크로 감싸져있다면, Cell을 터치하면 다른 뷰로 이동이 가능하다.

Array로 구성된 데이터를 List로 표시하기

List에 표시될 Array의 내용물은 반드시 Identifiable 프로토콜을 따르는 class, struct로 정의 해야한다

struct Item: Identifiable {
    var id = UUID() // Int, String 등 hashable을 따르는 것들은 뭐든지 가능
    let name: String
} 

List 내에서 식별될 수 있는 id 프로퍼티도 반드시 가지고 있어야 한다 !

struct ContentView: View {
    let items: [Item] = [Item(name: "asdf"), Item(name: "sadfasf")]

    var body: some View {
        NavigationView {
            List(items) {item in
                Text(item.name)
            }
            .listStyle(GroupedListStyle())
            .navigationBarTitle("List View")
        }
    }
}

List 항목 삭제 수정

우선 items가 상태 프로퍼티로 존재해야한다. 따라서 위의 코드의 items를 @State private var items 로 바꿔주고 아래 코드처럼 함수를 추가해주면 된다.

List {
    // ForEach로 각각의 cell에 delete 메소드를 입력시켜주어야한다.
    ForEach(items) { item in
            Text(item.name)
        }.onDelete(perform: didDeleteCell)
    }
    .listStyle(GroupedListStyle())
    .navigationBarTitle("List View")

// parameter 에는 반드시 offsets 을 추가해야 함.
func didDeleteCell(at offsets: IndexSet) {
    items.remove(atOffsets: offsets) 
    print("Deleted cell is \(offsets)")
}

수정 코드(셀의 순서 변경 가능)

List {
    ForEach(items) { item in
        Text(item.name)
    }.onMove(perform: didMoveCell)
}
.listStyle(GroupedListStyle())
.navigationBarTitle("List View")
.navigationBarItems(trailing: EditButton()) // 수정 버튼이 들어갈 Item

func didMoveCell(form source: IndexSet, to destination: Int) {}

참조

https://unnnyong.com/2020/05/23/swiftui-uikit-%ea%b3%bc-%ed%95%a8%ea%bb%98-%ec%82%ac%ec%9a%a9%ed%95%98%ea%b8%b0-uiviewrepresentable-uiviewcontrollerrepresentable-uihostingcontroller/#comment-25

https://stackoverflow.com/questions/56938805/how-to-pass-one-swiftui-view-as-a-variable-to-another-view-struct?rq=1

https://unnnyong.com/2020/05/22/swiftui-list-%ea%b5%ac-uitableview/

profile
hi there 👋

0개의 댓글