Daily App part3

Eason
彼得潘的 Swift iOS / Flutter App 開發教室
16 min readJun 8, 2023

延續

接下來製作 Map Places 利用定位找自己附近的餐廳,但是 sreach 部分沒有完成之後再來研究。

要先使用 CocoaPods 安裝 pod GooglePlaces ,點擊 Terminal 到專案底下後輸入 pod install 完成安裝。

寫一個 Class 專門呼叫 API

class GoogleMap {

static let apiKey = "AIzaSyDUsvvw1-4Liict7_IA_cDFPEYrl77QajU"

static let shared = GoogleMap()

func getMap (location: String,completion: @escaping (Result<[MapInfo], Error>) -> Void) {
let urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(location)&radius=1000&type=restaurant&language=zh-TW&key=\(GoogleMap.apiKey)"

guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { data , response, error in
if let data = data {
do {
let result = try JSONDecoder().decode(MapModel.self, from: data)
completion(.success(result.results))
} catch {
completion(.failure(error))
}
}
}.resume()
}

func sreachMap (keyword:String,completion: @escaping (Result<[MapInfo], Error>) -> Void ) {
let urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=25.0338,121.5646&radius=1000&keyword=\(keyword)&language=zh-TW&key=\(GoogleMap.apiKey)"

guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { data , response, error in
if let data = data {
do {
let result = try JSONDecoder().decode(MapModel.self, from: data)
completion(.success(result.results))
} catch {
completion(.failure(error))
}
}
}.resume()
}

func detailMap (place_id :String , completion:@escaping (Result<DetailInfo ,Error>) -> Void) {
let urlString = "https://maps.googleapis.com/maps/api/place/details/json?place_id=\(place_id)&key=\(GoogleMap.apiKey)"

guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { data , response, error in
if let data = data {
do {
let results = try JSONDecoder().decode(Detail.self, from: data)
print(results)
completion(.success(results.result))
} catch {
completion(.failure(error))
}
}
}.resume()
}

func getPhoto (photo_reference:String ,completion: @escaping (UIImage?) -> Void ) {
let urlString = "https://maps.googleapis.com/maps/api/place/photo?photo_reference=\(photo_reference)&key=\(GoogleMap.apiKey)&maxwidth=1600"


guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { data , response, error in
if let data = data {
do {
let result = UIImage(data: data)
print(result)
completion(result)
} catch {
completion(UIImage(systemName: "photo"))
}
}
}.resume()
}

在UITableViewController,需要設定地位,點擊 TableView的 Cell 會傳到一個 DetailViewController 。

class GoogleMapTableViewController: UITableViewController,CLLocationManagerDelegate {


var locationManager: CLLocationManager!

var mapInfo = [MapInfo]()

@IBOutlet weak var selectBar: UISearchBar!

let loadingView = UIView()
let activityIndicator = UIActivityIndicatorView()
let loadingLabel = UILabel()

override func viewDidLoad() {
super.viewDidLoad()

selectBar.delegate = self
// 初始化定位管理器
locationManager = CLLocationManager()
locationManager.delegate = self

// 請求定位權限
locationManager.requestWhenInUseAuthorization()
setActivityIndicatorView()
hideActivityIndicatorView()
}

// 請求定位權限的回調方法
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
// 開始定位更新
locationManager.startUpdatingLocation()
}
}

// 接收定位更新的回調方法
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
print(location.coordinate.latitude)
print(location.coordinate.longitude)
let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude

GoogleMap.shared.getMap(location: "\(latitude),\(longitude)") { result in
switch result {
case .success(let mapModels):
print(mapModels)
self.mapInfo = mapModels
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failure(let error):
print(error)
}
}


// 獲取到所需的位置資訊後,停止定位更新
locationManager.stopUpdatingLocation()
}
}


// 處理定位錯誤的回調方法
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("定位錯誤:\(error.localizedDescription)")
}

// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return mapInfo.count
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "\(GoogleMapTableViewCell.self)", for: indexPath) as! GoogleMapTableViewCell

let mapInfo = mapInfo[indexPath.row]
// Configure the cell...
cell.nameLabel.text = mapInfo.name
cell.DetailLabel.text = mapInfo.vicinity
cell.iconImage.image = UIImage(systemName: "photo")

GoogleMap.shared.getPhoto(photo_reference: mapInfo.photos[0].photo_reference) { image in
if let image = image {
DispatchQueue.main.async {
cell.iconImage.image = image
}
}
}


return cell
}


@IBSegueAction func showDetail(_ coder: NSCoder) -> DetailViewController? {
guard let controller = DetailViewController(coder: coder) else {return nil}
guard let row = tableView.indexPathForSelectedRow?.row else {return nil}
controller.mapInfos = mapInfo[row]

return controller
}

}

}

在DetailViewController 裡面使用了 UITableView 與 CollectionView ,UITableView 顯示客人的留言,而 CollectionView 顯示餐廳的圖片。

class DetailViewController: UIViewController {


@IBOutlet weak var photoCollection: UICollectionView!

@IBOutlet weak var DescriptionLabel: UILabel!

@IBOutlet weak var reviewsTable: UITableView!


@IBOutlet weak var titleName: UINavigationItem!

var mapInfos: MapInfo?
var photo: [Photo]?
var reviews: [Reviews]?
var detailInfo : DetailInfo?
var dateFormatter = DateFormatter()

override func viewDidLoad() {
super.viewDidLoad()

photoCollection.delegate = self
photoCollection.dataSource = self
reviewsTable.dataSource = self
reviewsTable.delegate = self

titleName.title = mapInfos?.name
DescriptionLabel.text = mapInfos?.vicinity


GoogleMap.shared.detailMap(place_id: mapInfos?.place_id ?? "") { result in
switch result {
case .success(let detailInfos):
self.detailInfo = detailInfos
DispatchQueue.main.async {
self.photoCollection.reloadData()
self.reviewsTable.reloadData()
}
case .failure(let error):
print(error)
}
}

}

}
extension DetailViewController:UITableViewDelegate,UITableViewDataSource {

func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return detailInfo?.reviews.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "\(ReviewsTableViewCell.self)", for: indexPath) as! ReviewsTableViewCell

cell.authorName.text = detailInfo?.reviews[indexPath.row].author_name
cell.authorTextLabel.text = detailInfo?.reviews[indexPath.row].text
let time = detailInfo?.reviews[indexPath.row].time ?? Date()
let timeInterval = time.timeIntervalSince1970
let date = Date(timeIntervalSince1970: timeInterval)
self.dateFormatter.dateFormat = "yyyy年MM月dd日 HH:mm"
cell.timeLabel.text = dateFormatter.string(from: date)


return cell
}


}
extension DetailViewController: UICollectionViewDelegate, UICollectionViewDataSource {

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return detailInfo?.photos.count ?? 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(PhotoCollectionViewCell.self)", for: indexPath) as! PhotoCollectionViewCell

cell.storePhoto.image = UIImage(systemName: "placeholder")

GoogleMap.shared.getPhoto(photo_reference: detailInfo?.photos[indexPath.row].photo_reference ?? "") { image in
DispatchQueue.main.async {
cell.storePhoto.image = image
}
}
return cell
}
}

參考:

作品:

--

--