به نام خدا

امروزه هر پروژه ای که یه برنامه نویس میخواد بگیره یه چیزایی توی مایه های اسنپ شده یعنی تا میخوام توضیح پروژه رو بخونم میبینم نوشته یه پروژه شبیه اسنپ

کاری به این ندارم که هرموقع یه چیزی مورد استقبال مردم قرار میگیره همه میخوام یکی مثل اونو بسازن

و کاری هم ندارم که اسنپ خوبه یا بده

فقط قراره باهم یاد بگیریم چجوری میشه با نقشه گوگل توی ای او اس کار کرد.

فکر کنم تا الان با CocoaPod کار کرده باشید یا اگه نکردید یه سرچ بزنید و روی مک نصب کنید

این دو خط رو به پاد اضافه کنید

pod 'GoogleMaps'
pod 'GooglePlaces'

و بعدشم طبق معمول باید از دستور pod install استفاده کنید

حالا پروژتون رو باز کنید تا وارد بخش کد و اینترفیس بشیم

اول وارد main.soryboard بشید و یه ویو به درون صفحه بکشید و از سمت راست اون بالا Identifier inspector کلاس اون ویو رو به GMSMapView تغییر بدید

همین ویو که ساختید رو به بخش کدتون وصل کنید یعنی یه OUTLET توی کد ازش بسازید

حالا کارمون شروع میشه

۱. وارد کنسول توسعه دهندگان گوگل بشید (دقت کنید که تحریم هستیم) 

۲. گزینه Enable API and Service رو بزنید و دو تا مورد زیر رو فعال کنید

  • Google Maps SDK for iOS
  • Google Places API Web Service

خب حالا گزینه Credentials رو پیدا کنید و بزنید 

به دنبال گزینه Create credentials بگردید و بعد از اینکه پیدا کردید یه API key جدید ایجاد کنید

به صفحه زیر که رسیدید کد داده شده رو کپی کنید

 

خب کارمون با کنسول تموم شد میتونید فیلتر شکن رو خاموش کنید

وارد محیط ایکس کد بشید و توی پروژتون فایل AppDelegate.swift. رو پیدا کنید و واردش بشید

اول GoogleMaps رو ایمپورت کنید

import GoogleMaps

حالا به تابع didFinishLaunchingWithOptions میتونید یه خط زیر رو اضافه کنید

 GMSServices.provideAPIKey("YOUR-API-KEY")

دقت کنید چیزی که کپی کردید رو باید اینجا جایگزاری کنید.

خب تا اینجا ما هم به گوگل گفتیم یه برنامه میخوایم بسازیم و هم به برناممون گفتیم میخوایم به گوگل وصل بشیم

یه نکته اضافی: اگه توی آموزش ها دیدید که به بخش Capabalities میرن و Maps رو فعال میکنن برای اینه که میخوان از MapKit خود IOS استفاده کنن و ربطی به چیزی که ما داریم یاد میگیریم نداره!

تا اینجا اگه برنامه رو اجرا کنید میبینید یه نقشه هست که میتونید توش اسکرول کنید زوم کنید و جاهای مختلف رو ببینید اما ما میخوایم یه گزینه داشته باشه که وقتی روش بزنیم مکان فعلی رو نشون بده و بتونیم یه پین بزاریم روی نقشه و دو تا مکان مختلف رو نشون بدیم تا فاصله و راه رو نشون بده و یه هزینه ای هم تخمین بزنه و..

خوشبختانه کدهایی که قراره یاد بگیریم هم خیلی کم هستن هم قابل درک برای هممون هست

وارد ویوکنترلر بشید یه متغییر زیر رو به کلاستون اضافه کنید (البته ثابت هست نه متغییر)

 let locationManager = CLLocationManager()

خب این یه متغییر معمولی نیست کلاسی که توشه به ما قراره خیلی چیزا رو بده که مهمترین مکان فعلی کاربر هست که قراره به صورت طول و عرض جغرافیایی از گوشی کاربر بگیریم

هنوز وارد مبحث نقشه گوگل نشدیم و کدی براش نزدیم فعلا فقط یه متغییر تعریف کردیم

باید بدونید که برای استفاده از این قابلیت که مکان کاربر رو داشته باشید باید از کاربر اجازه یا همون دسترسی بگیرید برای اینکار اول وارد فایل info,plist بشید و در آخر این فایل یه چیز به اسم Privacy - Location When In Use Usage Description ایجاد کنید و رو به روش یه متنی رو که میخواید به کاربر نشون داده بشه بنویسید

بعد برگردید همون ویو کنترلری که بودیم و برای اینکه به کاربر درخواست بدیم باید کد زیر رو به viewDidLoad اضافه کنیم

   manager.delegate = self
   manager.requestWhenInUseAuthorization()

خط اول برای اینه که وقتی کاربر قبول یا رد کرد بتونیم بفهمیم یعنی به کلاس خودمون برگرده و نتیجش اعلام بشه (علاوه بر این یه توابع خوب دیگه ای هم داره که در ادامه میبینم باهم)

خط دوم هم که درخواست رو به کاربر میده

حالا برای تمیزی کد میتونیم یه اکستنشن بنویسیم و کلاس ویوکنترلرمون رو از CLLocationManagerDelegate ارث بری کنیم

و توی اون دو تابع مهم رو بنویسیم

extension MapViewController: CLLocationManagerDelegate {
 
  func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    
    guard status == .authorizedWhenInUse else {
      return
    }
   
    locationManager.startUpdatingLocation()
      
  
    mapView.isMyLocationEnabled = true
    mapView.settings.myLocationButton = true
  }
  
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.first else {
      return
    }
    mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
    locationManager.stopUpdatingLocation()
  }
}

خب شروع میکنم به توضیح این دو تابع

تابع اول زمانی صدا زده میشه که کاربر درخواست ما رو برای دسترسی به موقعیت فعلی٬ رد یا قبول کنه که ما یک گارد گذاشتیم که فقط ادامه تابع زمانی اجرا میشه که کاربر تایید کرده باشه در ادامه اوکیشن منیجر رو روشن میکنیم یعنی اینکه کاربر اگه از این به بعد تکون بخوره ما متوجه میشیم که داره حرکت میکنه اما از کجا متوجه میشیم؟

هرلحظه که مکان کاربر تغییر کنه تابع پایینیش اجرا میشه که توی اون تابع پایینی هم ما اولین لوکیشن رو از آرایه گرفتیم و بعد با GMSCameraPosition مختصات لوکیشن و یه سری چیزا مثل زاویه و زوم و... رو دادیم و بعد دوربین رو به مپ ویو (همون ویویی که توی اینترفیس بیلدر ساختید) دادیم که نشونمون بده

بعدشم اون چیزی که روشن کرده بودیم خاموش میکنیم که دیگه کاربر اگرم حرکت بکنه دیگه این تابع صدا زده نشه یعنی ما فقط میخواستیم مکان فعلیشو بگیریم همین

اون دو خطی هم که درموردش توضیح ندادم توی تابع اولی کارشون اینه که خط کد اول یه دایره آبی کوچیک روی نقشه نشون میده که منظور خود شما هستید و خط پایینیش هم یه دکمه به پایین سمت راست اضافه میکنه که وقتی روش بزنید دوربین به سمت مکان شما میاد

نکته: دوربین وسط نقشه رو نقطه اصلی میگیره پس شما هرکاری دارید با همون وسطِ وسط هست

حالا اگر برنامه رو اجرا کنید میبینید که میتونه مکان شما رو نشون بده

 

.::چند تابع مهم و کاربردی::.

هر وقت یه اکستنشن دیگه برای همون کلاس ویو کنترلر نوشتید و از کلاس GMSMapViewDelegate ارث بری کردید چندتا تابع خیلی خیلی کاربردی رو میتونید استفاده کنید

یادتون نره حتما کد زیر رو هم قبلش توی viewDidLoad اضافه کنید

gmsView.delegate = self

۱. مثلا idleAt position: که هرموقع که شما یه جا ساکن ایستادید روی نقشه و اسکرول نکردید فراخوانی میشه و مکان نقطه وسط در وسط (جایی که دوربین داره نشون میده) رو به شما میده

۲. :willMove gesture به محض اینکه کاربر یا خود برنامه٬ نقشه رو اسکرول کنه این تابع فراخوانی میشه (کاربردش برای زمان هایی هست که میخواید حین اسکرول چیزی رو پنهان کنید یا لودینگ بیارید تا وقتی که کامل روی یه نقطه دوربین ثابت بشه و تابع قبلی صدا زده بشه)

۳. didTap: همونجوری که از اسمش پیداست قراره مکانی که شما دقیقا روش کلیک میکنید رو به شما بده پس تابع مفیدی به نظر میاد

حالا چند نکته:

۱. هرموقع خواستید مثلا اگه کلیدی زده شد (مثل اسنپ که یه پین ثابت روی صفحه در نظر گرفته و وقتی روش کلیک بشه مکان زیر پین به عنوان مبدا یا مقصد انتخاب میشه) مکانی که دوربین روش هست رو بگیرید باید از خود ویویی که ساختید بگیرید

mapView.camera.target

خب یه وقتایی لازم میشه یه مکانی رو روی نقشه پین کنید این کار خیلی سادست

let marker = GMSMarker(position: <CLLocationCoordinate2D>)
marker.map = mapView

خب یه وقتایی میخواید پین شما شکل خاصی داشته باشه که باید یه کلاس بسازید و از GMSMarker ارث بری کنید و توی سازنده کلاس حتما یه سری متغییر اصلی مثل متغییر های زیر رو مقدار دهی کنید

    position = place.coordinate
    icon = UIImage(named: place.placeType+"_pin")
    groundAnchor = CGPoint(x: 0.5, y: 1)
    appearAnimation = .pop

که اینجوری میتونید تصویری که قراره به جای پین نشون بده رو شخصی سازی کنید

خب داستان تقریبا تمومه.

اگه یکم باهوش باشید قطعا با همین دانسته ها میتونید نقشه ای که توی اسنپ استفاده شده رو پیاده کنید

برای کشیدن راه بین دو نقطه هم فکر میکنم گوگل یه پولی رو میگیره اما کدش رو براتون میزارم

  func fetchMapData() {
        
        let directionURL = "https://maps.googleapis.com/maps/api/directions/json?" +
            "origin=\(des.latitude),\(des.longitude)&destination=\(tar.latitude),\(tar.longitude)&" +
        "key=YOUR-TOKEN"
        print(directionURL)
        
        
        Alamofire.request(directionURL).responseJSON
            { response in
                
                if let JSON = response.result.value {
                    
                    let mapResponse: [String: AnyObject] = JSON as! [String : AnyObject]
                    
                    let routesArray = (mapResponse["routes"] as? Array) ?? []
                    
                    let routes = (routesArray.first as? Dictionary<String, AnyObject>) ?? [:]
                    
                    let overviewPolyline = (routes["overview_polyline"] as? Dictionary<String,AnyObject>) ?? [:]
                    let polypoints = (overviewPolyline["points"] as? String) ?? ""
                    let line  = polypoints
                    
                    self.addPolyLine(encodedString: line)
                }
        }
        
    }
    
    func addPolyLine(encodedString: String) {
        
        let path = GMSMutablePath(fromEncodedPath: encodedString)
        let polyline = GMSPolyline(path: path)
        polyline.strokeWidth = 5
        polyline.strokeColor = .blue
        polyline.map = mapView
        
    }

بازم میگم فکر کنم باید یه مبلغی رو بدید به گوگل که این مجوز استفاده رو بهتون بده 

در مورد کد هم باید بگم برای استفادش باید Alamofire رو اضافه کنید

 

::::خورده کاری های که بعدا به دردتون میخوره

کد زیر میتونه طول و عرض جغرافیایی رو تبدیل به آدرس کنه

geoCoder.reverseGeocodeCoordinate(location) {response,error in
            guard let address = response?.firstResult() , let lines = address.lines else {return}
            self.addressLabel.text = lines.joined(separator: "\n")
}

یه قابلیت کوچیک اما خوب دیگه هم اینه که یه وقتایی لازم میشه اون دکمه ای که قراره وقتی روش کلیک کردیم ما رو توی نقشه نشون بده رو با پایین فاصله بدیم و این کار با کد زیر انجام میشه

gmsView.padding = UIEdgeInsets(top: self.view.safeAreaInsets.top, left: 0,
                                                bottom: labelHeight, right: 0)

خب دیگه حرفی نمونده

آهان راستی اینا همش GoogleMaps بود برای GooglePlace یه چیزایی هست

مثلا اینکه اسمی که شما میزنید رو کامل کنه یا یه سری عکس از اونجا بهتون بده یا کافه های نزدیک شما رو پیدا کنه که اونم خیلی ساده است و با کلاس 

GoogleDataProvider()

و تابع 

private func fetchNearbyPlaces(coordinate: CLLocationCoordinate2D) {
  // 1
  mapView.clear()
  // 2
  dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
    places.forEach {
      // 3
      let marker = PlaceMarker(place: $0)
      // 4
      marker.map = self.mapView
    }
  }
}

قابل حل هست به همین سادگی

اگه خواستید این آموزش رو دقیق تر و کامل تر هم مطالعه کنید میتونید به اینجا سر بزنید

سوالی بود بپرسید اگه بتونم پاسخ میدم

به امید موفقیت تک تک دوستان

یاعلی