본문 바로가기

카테고리 없음

SwiftUI_Tutorial 간단 정리(3/3) - App design and layout & Interfacing with UIKit 앞에 뷰 구성 부분

이전 글: https://dev-doogie.tistory.com/24

Working with UI controls

.onAppear => ViewDidAppear
.onDisappear => ViewDidDisappear
같은 느낌인데 VC에서 사용하고 뭐 그런게 아니라 모든 뷰에 적용 가능 한듯

 

@Environment(\.editMode)
@Environment에는 \.editMode라는 기본적으로 제공하는 프로퍼티가 있는데 이는 EditButton()을 추가하면 이 버튼과 자동으로 연결됨
그리고 editMode프로퍼티는 wrappedValue가 .active인지 .inactive인지로 구별함

 

 

A뷰 안에 상위 뷰로 부터 주입되어야 하는 @Environment 프로퍼티를 사용하는 B뷰가 있다고 가정해볼 때
A뷰에서 해당 프로퍼티를 사용하지 않더라도 하위 뷰로 연결을 시켜야하기 때문에 A뷰 생성시 .environment로 해당 값을 전달하여야 함

 

Interfacing with UIKit

UIKit 연결 정리에 앞서 신기하거나 의문이 들었던 점

View 구성할 프리뷰가 상위 까지 염두하고 보여주지 않으니 프리뷰에서 내가 나중에 실제로 보게 때와 비슷하게 조건을 걸어서 표시하기도

 

gradient를 추가했다고 외 Vstack이 왼쪽 아래로 가는지 잘 모르겠음

추측으로는 gradient를 추가하기 전에는 Zstack을 차지하는 범위가 Vstack 이고 그 크기가 fit(?)해서 중앙에 배치되었는데

gradient 화면 전체를 차지하고 이에따라 ZStack 범위가 화면 전체만큼 넓어져서 .bottomLeading 명시적으로 보이는 ?

 

UIKit 기능을 SwiftUI에서 사용하기

온전히 SwiftUI로만 모든걸 구현하기에는 한계가 있어 일정 부분은 UIKit 힘을 빌려야하는데 튜토리얼에서는 UIPageViewController와 UIPageControl을 예시로 들었다

 

- UIViewController

 

`UIViewControllerRepresentable` 채택하는 객체를 만들고 `makeUIViewController` `updateUIViewController`라는 필수 메서드를 구현한다

 

그리고 dataSource, delegate등을 사용하기 위한 Coordinator도 구현한다(디자인 패턴 아님!)

 

makeUIViewController: 사용하려는 ViewController를 반환하며 화면에 보여질 때 딱 한 번만 호출됨

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
            
            //dataSource, delegate 채택하는 부분
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        return pageViewController
    }

 

updateUIViewController: SwiftUI에서 사용하는 UIKit의 기능에 영향을 미치는 변경이 있으면 호출되고 해당 UIKit 기능에도 반영

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [context.coordinator.controllers[currentPage]], direction: .forward, animated: true)
    }

 

 

makeCoordinator: makeUIViewController가 호출되기 전에 호출되는 메서드, 기본적으로는 아무것도 반환하지 않지만 delegate, dataSource등을 사용하기 위해(예를 들면 해당 튜토리얼에서는 UIPageViewController의 dataSource) 해당 delgate혹은 dataSource를 채택한 객체를 반환하면 위 makeUIViewControlle와 updateUIViewController메서드의 파라미터인 context의 coordinator에서 확인 가능

classCoordinator: NSObject <- 이 객체 만들기만 해도 필수 구현하라고 에러 뜸

 

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageViewController
        var controllers = [UIViewController]()
        
        init(_ parent: PageViewController) {
            self.parent = parent
            controllers = parent.pages.map { UIHostingController(rootView: $0) }
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = controllers.firstIndex(of: viewController) else {
                return nil
            }
            
            if index == 0 {
                return controllers.last
            }
            
            return controllers[index - 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = controllers.firstIndex(of: viewController) else {
                return nil
            }
            
            if index == controllers.count - 1 {
                return controllers.first
            }
            
            return controllers[index + 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
               let visibleViewController = pageViewController.viewControllers?.first,
               let index = controllers.firstIndex(of: visibleViewController) {
                parent.currentPage = index
            }
        }
    }

 

- UIView

 

UIViewController와 거의 비슷하게 UIViewRepresentable를 채택 하고 필수 메서드인 makeUIViewupdateUIView 메서드를 구현한다

 

여기서는 delegate와 dataSource가 아닌 @objc 메서드를 사용하기 위해 Coordinator를 구현했다

import SwiftUI
import UIKit

struct PageControl: UIViewRepresentable {
    var numberOfPages: Int
    @Binding var currenPage: Int
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    func makeUIView(context: Context) -> UIPageControl {
        let control = UIPageControl()
        control.numberOfPages = numberOfPages
        control.addTarget(
            context.coordinator,
            action: #selector(Coordinator.updateCurrentPage),
            for: .valueChanged)


        return control
    }
    
    func updateUIView(_ uiView: UIPageControl, context: Context) {
        uiView.currentPage = currenPage
    }
    
    class Coordinator: NSObject {
        var control: PageControl
        init(_ control: PageControl) {
            self.control = control
        }
        
        @objc func updateCurrentPage(sender: UIPageControl) {
            control.currenPage = sender.currentPage
        }
    }
}

 

그리고 SwiftUI View에 @State 프로퍼티를, Representable객체에 @Binding프로퍼티를 만들고 변경되는 값을 바로 뷰에 반영할 수 있다