Swift 다 중 맵 원본 업무 기반 맵 컨트롤 구현 (2): 사용자 정의 UI 표시

7693 단어
지도 원본 을 봉인 한 후에 우 리 는 가장 자주 사용 하 는 기능 을 실현 하고 UI 디 스 플레이 를 사용자 정의 하기 시작 했다.여기 서 나 는 표 시 를 예 로 들 었 다.사용자 정의 UI 는 CoreGraphic 으로 그 릴 수도 있 고 전통 적 인 UIKit 로 그 릴 수도 있 습 니 다.사용자 정의 로 표 시 된 스타일 은 복잡 하지 않 습 니 다. 전통 적 인 UIView 로 보 여 드 리 겠 습 니 다.물론 그래 픽 UI 가 있 습 니 다. 저도 CoreGraphic 에 유용 합 니 다.
제 사용자 정의 로 그림 외 에 도 보 여 드릴 정보 가 있 기 때문에 데이터 구조 로 표 시 된 정 보 를 표시 합 니 다.
public enum MeshAnnotationType {
    case homePoint
}

public class MeshMapAnnotation {
    public var type: MeshAnnotationType

    init(type: MeshAnnotationType) {
        self.type = type
    }
}

간단 한 레이 블 스타일 이기 때문에 View 의 실현 도 간단 합 니 다.
class MeshAnnotaionView: UIView {
    private(set) var annotion: MeshMapAnnotation

    private(set) var imageView = UIImageView(image: nil)
    
    init(annotion: MeshMapAnnotation) {
        self.annotion = annotion
        super.init(frame: CGRect.zero)
        addSubview(imageView)
        setupUI()
    }
    
    private func setupUI() {
        switch type {
        case .homePoint:
            imageView.image = Asset.Map.iconMapHomepoint.image
            imageView.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
            bounds = imageView.frame
        default:
            break
        }
    }
}

이곳 의 스타일 은 간단 한 icon 입 니 다.표 시 된 스타일 은 여기 서 설명 하지 않 겠 습 니 다. (중점 이 아 닙 니 다) 어쨌든 자신 이 하나의 View 를 실현 한 것 입 니 다.다음 단 계 는 사용자 정의 UI 를 관리 하기 위해 사용자 정의 MapOverlayView 를 정의 합 니 다.
class CustomMapOverlayView: UIView {
    
    private var homePointAnnotationView: MeshAnnotaionView?

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clear
        isUserInteractionEnabled = false
    }
    
    func updateHomePoint(_ point: CGPoint?) {
        if let point = point {
            if homePointAnnotationView == nil {
                let annotation = MeshMapAnnotation(type: .homePoint)
                homePointAnnotationView = MeshAnnotaionView(annotion: annotation)
                addSubview(homePointAnnotationView!)
            }
            homePointAnnotationView?.center = point
        } else {
            homePointAnnotationView?.removeFromSuperview()
        }
    }
}

Custom MapOverlayView 의 디 테 일 은 isUser InteractionEnabled 를 false 로 설정 해 야 합 니 다. 이 층 은 지도 원본 상단 에 덮 여 있 기 때문에 대화 이벤트 에 도 응답 하면 사용 자 는 지 도 를 드래그 하고 크기 를 조정 할 수 없습니다.이 View 에 표 시 된 사용자 정의 관 리 는 각자 의 업무 장면 에 달 려 있다.내 가 있 는 지도 에는 몇 가지 유형 이 표시 되 어 있 기 때문에 선택 할 수 있 는 속성 으로 직접 정의 되 었 다.상부 에 더 큰 유연성 을 주 려 면 사전 으로 저장 할 수도 있다.
현재 이 구조 에서 우리 의 사용자 정의 표 시 는 지도 에서 벗 어 나 단독으로 테스트 할 수 있다.테스트 항목 에서 CustomMapOverlayView 를 직접 초기 화하 고 updateHomePoint 를 호출 하면 homepointView 를 렌 더 링 할 수 있 습 니 다.UI 요 소 를 사용자 정의 하면 유닛 테스트 를 잘 지원 할 수 있 습 니 다.이것 도 디자인 할 때 한 가 지 를 고려 하여 모든 단원 이 가능 한 한 안 으로 모여 야 한다.외부 와 데이터 연결 을 통 해 자신의 논 리 를 독립 적 으로 실행 할 수 있 습 니 다.이렇게 하면 마지막 전체적인 구 조 는 각 작은 단원 이 연결 되 는 것 이지 한 무더기 의 단원 이 직접 용접 되 어 죽 는 것 이 아니다.
다음은 사용자 정의 MapOverlayView 를 지도 컨트롤 에 통합 합 니 다.
public class MeshMapView: UIView {

    let customOverlayView: CustomMapOverlayView
    
    public init() {
        customOverlayView = CustomMapOverlayView(frame: CGRect.zero)
        super.init(frame: CGRect.zero)
        addVendorMapView()
        
        addSubview(customOverlayView)
        customOverlayView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
    }
}

사용자 정의 UI 층 이 맵 원본 위 에 있어 야 하기 때문에 지 도 를 추가 하고 사용자 정의 View 를 추가 해 야 합 니 다.
통합 후 외부 호출 에 인 터 페 이 스 를 노출 할 수 있 습 니 다:
public class MeshMapView: UIView {

    public var homePoint: CLLocationCoordinate2D? {
        didSet {
            updateHomePoint(homePoint)
        }
    }

    private func updateHomePoint(_ coordinate: CLLocationCoordinate2D?) {
        let correspondingPoint = convertCoordinateToCustomOverlayView(coordinate: coordinate)
        customOverlayView.updateHomePoint(correspondingPoint)
    }

    private func convertCoordinateToCustomOverlayView(coordinate: CLLocationCoordinate2D?) -> CGPoint? {
        guard let coordinate = coordinate else { return nil }
        let standardCoordindate = MeshMapView.convertCoordinateToGCJIfNeeded(coordinate: coordinate)
        guard let point = map?.convert(coordinate: standardCoordindate, toPointTo: customOverlayView) else { return nil }
        if point.x.isNaN || point.y.isNaN { //                    
            return nil
        }
        return point
    }
}

국내 지도 에서 사용 하 는 좌 표 는 모두 GCJ 이지 만 국제 적 으로 GPS 좌표 가 많이 존재 하기 때문에 이곳 은 좌 표를 바 꿀 때 하나의 인 터 페 이 스 를 호출 하여 WGS 84 를 GCJ 로 바 꾸 었 다. 물론 이곳 의 오 차 는 분명 있 을 것 이다.위의 코드 중점 은 표 시 된 지리 좌 표를 저장 하고 customeOverlayView 에 추가 하기 전에 지리 좌 표를 평면 좌표 로 변환 해 야 한 다 는 것 이다.변환 이 완료 되면 customeOverlayView. updateHomePoint 를 호출 할 수 있 습 니 다.
또 하나의 디 테 일 은 지리 좌표 에서 평면 좌표 로 전환 하 는 것 으로 지도 가 다 로드 되 지 않 으 면 전환 에 실패 할 수도 있다.CGPoint 는 값 형식 이기 때문에 어떤 맵 은 SDK 변환 에 실패 하면 NaN 으로 값 이 전 환 됩 니 다.전환 후 x 와 y 의 값 이 유효한 지 판단 해 야 한다.
위의 코드 를 완성 한 후 updateHomePoint 를 호출 하면 표 시 된 위 치 를 보 여줄 수 있 습 니 다.그러나 현재 이 실현 에 또 하나의 문제 가 있 습 니 다. 사용자 가 지 도 를 이동 할 때 보기 에 표 시 된 위 치 는 변동 이 없습니다.정확 한 반응 은 지도 위치 가 바 뀌 었 고 표 시 된 위치 도 함께 바 뀌 었 을 것 이다.자 연 스 럽 게 지도 구역 변화 통 지 를 감청 하고 표 시 된 위 치 를 업데이트 해 야 합 니 다.
우선, 우 리 는 지도 원 으로 하 는 대리 대상 을 설명 합 니 다.
protocol VendorMapDelegate: class {
    func mapViewDidChange()
    func mapInitComplete()
}

class VendorMapDelegateProxy: NSObject, MAMapViewDelegate {
    
    weak var delegate: VendorMapDelegate?

    init(vendorMapDelegate: VendorMapDelegate) {
        self.delegate = vendorMapDelegate
        super.init()
    }
    
    func mapViewRegionChanged(_ mapView: MAMapView!) {
        delegate?.mapViewDidChange()
    }
    
    func mapInitComplete(_ mapView: MAMapView!) {
        delegate?.mapInitComplete()
    }
}

지도 원 의 알림 이 벤트 를 표시 하기 위해 일반적인 인터페이스 인 VendorMapDelegate 를 발 표 했 습 니 다.매 순간 하나의 맵 소스 만 존재 하기 때문에 VendorMapDelegate Proxy 도 하나의 인 스 턴 스 만 MeshMapView 와 연 결 됩 니 다.mapInitComplete 방법 은 고 덕 특유 의 것 입 니 다. 서로 다른 지도 SDK 는 자신 이 불 러 온 것 을 표시 하 는 방식 이 다 릅 니 다. 어떤 것 은 finishLoading 이 고, 고 덕 은 mapInitComplete 입 니 다.지도 로 딩 이 완 료 된 사건 에 대해 서도 외부 에서 관심 을 갖 기 때문에 이 방법 도 밝 혔 다.
이어서 우 리 는 대리 대상 을 MeshMapView 에 통합 시 켰 다.
public class MeshMapView: UIView {

    public var homePoint: CLLocationCoordinate2D? {
        didSet {
            updateHomePoint(homePoint)
        }
    }

    private lazy var mapDelegateProxy: VendorMapDelegateProxy = {
        return VendorMapDelegateProxy(vendorMapDelegate: self)
    }()
    
    private func addVendorMapView() {
        switch MeshMapView.currentMapVendor {
        case .gaode:
            let gaodeMap = MAMapView(frame: CGRect.zero)
            gaodeMap.delegate = mapDelegateProxy
            addSubview(gaodeMap)
            gaodeMap.snp.makeConstraints { (make) in
                make.edges.equalToSuperview()
            }
            self.gaodeMap = gaodeMap
        case .baidu:
           // 。。。
        }
    }

   func refreshCustomOverlay() {
        updateHomePoint(homePoint)
    }
}

extension MeshMapView: VendorMapDelegate {
    func mapViewDidChange() {
        refreshCustomOverlay()
    }
    
    func mapInitComplete() {
        //。。。
    }
}


이 코드 를 통합 하면 homepoint 의 지리 좌 표를 저장 해 야 하 는 이 유 를 알 수 있 습 니 다. 지도 영역 이 변 한 후에 표 시 를 다시 렌 더 링 해 야 하기 때문에 메타 데이터 가 평면 좌 표를 다시 매 핑 하여 위 치 를 업데이트 해 야 합 니 다.
이 모듈 의 디자인 요점 은 CustomMapOverlayView 의 직책 을 반드시 명확 하 게 구분 하고 평면 좌표 업데이트 위치 만 받 아들 여야 한 다 는 것 이다.이렇게 하면 Custom MapOverlay View 는 업무 와 결합 할 수 있 고 표 시 된 그리 기 능력 만 제공 합 니 다.지도 컨트롤 은 좌표 변환 을 관리 하고 지도 구역 이 변 경 된 후 다시 렌 더 링 할 시 기 를 관리 해 야 합 니 다.

좋은 웹페이지 즐겨찾기