KakaoMapsSDK v2 iOS 지도화면이 실제 기기에서 블러처리 한 것 처럼 나옵니다

[FAQ] 지도/로컬 API 문의 전 꼭 읽어 주세요.
https://devtalk.kakao.com/t/faq-api/125610

안녕하세요. KakaoMapsSDK v2 로 마이그레이션 작업 중인데,
iOS 예제에 나온 Storyboard 기반으로 된 것을 코드 베이스로 지도 화면을 적용 중입니다.
그런데 시뮬레이터로 돌렸을 때와 실제 기기로 돌렸을 때에 나오는 지도화면이 달라서 문의 드립니다.

아래 코드는 샘플 예제에서 Storyboard 기반으로 나온 코드를 코드 베이스로 변경만 했습니다.
KMViewContainer의 초기값 ( mapContainer: KMViewContainer = KMViewContainer(origin: .zero, size: CGSisze(width: 300, height: 300) 으로 입력한 경우에는 잘 나옵니다.

import UIKit
import SnapKit
import KakaoMapsSDK

class TestViewController: UIViewController, MapControllerDelegate {
    
    deinit {
        mapController?.stopRendering()
        mapController?.stopEngine()
        
        print("deinit")
    }
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        //KMController 생성.
        mapController = KMController(viewContainer: mapContainer)!
        mapController!.delegate = self
        
        mapController?.initEngine() //엔진 초기화. 엔진 내부 객체 생성 및 초기화가 진행된다.
        view.addSubview(mapContainer)
        mapContainer.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        addObservers()
        _appear = true
        if mapController?.engineStarted == false {
            mapController?.startEngine()
        }
        
        if mapController?.rendering == false {
            mapController?.startRendering()
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        _appear = false
        mapController?.stopRendering()  //렌더링 중지.
    }

    override func viewDidDisappear(_ animated: Bool) {
        removeObservers()
        mapController?.stopEngine()     //엔진 정지. 추가되었던 ViewBase들이 삭제된다.
    }
    
    // 인증 성공시 delegate 호출.
    func authenticationSucceeded() {
        // 일반적으로 내부적으로 인증과정 진행하여 성공한 경우 별도의 작업은 필요하지 않으나,
        // 네트워크 실패와 같은 이슈로 인증실패하여 인증을 재시도한 경우, 성공한 후 정지된 엔진을 다시 시작할 수 있다.
        if _auth == false && _appear {
            _auth = true
            mapController?.startEngine()    //엔진 시작 및 렌더링 준비. 준비가 끝나면 MapControllerDelegate의 addViews 가 호출된다.
            mapController?.startRendering() //렌더링 시작.
        }
    }
    
    // 인증 실패시 호출.
    func authenticationFailed(_ errorCode: Int, desc: String) {
        print("error code: \(errorCode)")
        print("desc: \(desc)")
        _auth = false
        switch errorCode {
        case 400:
            showToast(self.view, message: "지도 종료(API인증 파라미터 오류)")
            break;
        case 401:
            showToast(self.view, message: "지도 종료(API인증 키 오류)")
            break;
        case 403:
            showToast(self.view, message: "지도 종료(API인증 권한 오류)")
            break;
        case 429:
            showToast(self.view, message: "지도 종료(API 사용쿼터 초과)")
            break;
        case 499:
            showToast(self.view, message: "지도 종료(네트워크 오류) 5초 후 재시도..")
            
            // 인증 실패 delegate 호출 이후 5초뒤에 재인증 시도..
            DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
                print("retry auth...")
                
                self.mapController?.authenticate()
            }
            break;
        default:
            break;
        }
    }
    
    func addViews() {
        //여기에서 그릴 View(KakaoMap, Roadview)들을 추가한다.
        let defaultPosition: MapPoint = MapPoint(longitude: 127.108678, latitude: 37.402001)
        //지도(KakaoMap)를 그리기 위한 viewInfo를 생성
        let mapviewInfo: MapviewInfo = MapviewInfo(viewName: "mapview", viewInfoName: "map", defaultPosition: defaultPosition, defaultLevel: 7)
        
        //KakaoMap 추가.
        if mapController?.addView(mapviewInfo) == Result.OK {
            print("OK") //추가 성공. 성공시 추가적으로 수행할 작업을 진행한다.
        }
    }
    
    //Container 뷰가 리사이즈 되었을때 호출된다. 변경된 크기에 맞게 ViewBase들의 크기를 조절할 필요가 있는 경우 여기에서 수행한다.
    func containerDidResized(_ size: CGSize) {
        log.info(size)
        log.info(mapContainer.bounds.size)
        let mapView: KakaoMap? = mapController?.getView("mapview") as? KakaoMap
        mapView?.viewRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)   //지도뷰의 크기를 리사이즈된 크기로 지정한다.
    }
    
    func viewWillDestroyed(_ view: ViewBase) {
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        log.info(mapContainer.bounds.size)
//        containerDidResized(mapContainer.bounds.size)
    }
    
    func addObservers(){
        NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
    
        _observerAdded = true
    }
     
    func removeObservers(){
        NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
        NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)

        _observerAdded = false
    }

    @objc func willResignActive(){
        mapController?.stopRendering()  //뷰가 inactive 상태로 전환되는 경우 렌더링 중인 경우 렌더링을 중단.
    }

    @objc func didBecomeActive(){
        mapController?.startRendering() //뷰가 active 상태가 되면 렌더링 시작. 엔진은 미리 시작된 상태여야 함.
    }
    
    func showToast(_ view: UIView, message: String, duration: TimeInterval = 2.0) {
        let toastLabel = UILabel(frame: CGRect(x: view.frame.size.width/2 - 150, y: view.frame.size.height-100, width: 300, height: 35))
        toastLabel.backgroundColor = UIColor.black
        toastLabel.textColor = UIColor.white
        toastLabel.textAlignment = NSTextAlignment.center;
        view.addSubview(toastLabel)
        toastLabel.text = message
        toastLabel.alpha = 1.0
        toastLabel.layer.cornerRadius = 10;
        toastLabel.clipsToBounds  =  true
        
        UIView.animate(withDuration: 0.4,
                       delay: duration - 0.4,
                       options: UIView.AnimationOptions.curveEaseOut,
                       animations: {
                                        toastLabel.alpha = 0.0
                                    },
                       completion: { (finished) in
                                        toastLabel.removeFromSuperview()
                                    })
    }
    
//    var mapContainer: KMViewContainer = KMViewContainer(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 300)))
    var mapContainer: KMViewContainer = KMViewContainer()
    var mapController: KMController?
    var _observerAdded: Bool = false
    var _auth: Bool = false
    var _appear: Bool = false
}

첫 번째가 실제 기기에서 스크린샷이고 두 번째가 시뮬레이터에서 스크린샷입니다.

IMG_2AEEE19EE27E-1
Simulator Screenshot - iPhone 13 mini - 2023-08-29 at 11.13.55

@rhkdgus0826 안녕하세요.
실제 지도의 렌더링은 KMViewContainer의 subview의 metalLayer 에서 수행됩니다.

  1. view hierarchy 에서 크기가 잘 반영되고 있는지
  2. containerResized delegate로 넘어오는 사이즈가 맞는지
    확인해보시면 될 것 같습니다.

@vectordev 알려주신 답변의 내용을 확인해 보았습니다.

  1. KMViewContainer > MTLMapContainer > CAMetalLayer 모두 크기는 (390.0, 840.0) 으로 동일합니다.
  2. containerDidResized 함수로 넘어오는 size 값도 (390.0, 840.0)으로 동일합니다.

오토레이아웃을 이용하는 방법으로 안 되면 초기값을 주는 방법으로 해야할 것 같습니다.
다른 방법으로 resize 가 제대로 되는지 확인할 수 있는 게 또 있을까요?

@vectordev
오토레이아웃을 사용하는 경우에 initEngine 을 UIViewController 의 viewDidLayoutSubviews에서 호출하니 이상이 없네요.