SwiftエラーType '' has no subscript membersを解決して、Dictionaryを現在地からの近さ順に並べたい
前提・実現したいこと
Swiftでグルメアプリを製作中なのですが、エラーがなかなか解決できずかなりつまづいています。解決法やアドバイスなど頂けると幸いです。
やりたいこと
FirebaseのFirestoreから取得したデータを元に、CoreLocationを用いて現在地から近い順にデータを並べ替え、TableViewに表示したい。
今やろうとしていることは、CoreLocationを用いて現在地と店舗との距離を測定し、keyをDistanceとしてデータに追加しようとしています。しかし下記のエラーが出てしまい、うまくいきません。
まずこの方法で現在地から近い順にソートできるかも自身もないため、そのほかにもやり方などあれば教えていただきたいです。
発生している問題・エラーメッセージ
Type 'Shop' has no subscript members
該当のソースコード
shopArrayにShop型のデータが突っ込んであります。
func locationManager(_ manager: CLLocationManager, didUpdateLOcations locations: [CLLocation]){
let localValue:CLLocationCoordinate2D = manager.location!.coordinate
print("locations =\(localValue.latitude) \(localValue.longitude)")
for i in 0 ..< self.shopArray.count{
let currentLocation : CLLocation = CLLocation(latitude: localValue.latitude, longitude: localValue.longitude)
let shopLocation : CLLocation = CLLocation(latitude: self.shopArray[i].latitude, longitude: self.shopArray[i].longitude)
let distance = shopLocation.distance(from: currentLocation)
shopArray[i]["distance"] = "distance" # エラーはこの行に出ます。
データの形式は下記です。
struct Shop {
var title: String
var recomendation: String
var tel: String
var station: String
var latitude: Double
var longitude: Double
var price: Int
var img: String
var dictionary: [String: Any] {
return [
"title": title,
"recomendation": recomendation,
"tel": tel,
"station": station,
"latitude": latitude,
"longitude": longitude,
"price": price,
"img": img
]
}
}
ーーーーーーーーーーーーーーーーーーーーーーーーーー
追記 shopArrayにデータを格納するコード
func loadData() {
let db = Firestore.firestore()
let shopsRef = db.collection("shops")
shopsRef.getDocuments() { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = snapshot{
for document in snapshot.documents {
print(document.data())
let data = document.data()
let title = data["title"] as? String ?? ""
let recomendation = data["recomendation"] as? String ?? ""
let tel = data["tel"] as? String ?? ""
let station = data["station"] as? String ?? ""
let latitude = data["latitude"] as? Double ?? Double()
let longitude = data["longitude"] as? Double ?? Double()
let price = data["price"] as? Int ?? Int()
let img = data["img"] as? String ?? ""
let distance = data["distance"] as? CLLocationDistance ?? Double()
let newShop = Shop(title: title, recomendation: recomendation, tel: tel, station: station, latitude: latitude, longitude: longitude, price: price, img: img, distance: distance)
self.shopArray.append(newShop)
func locationManager(_ manager: CLLocationManager, didUpdateLOcations locations: [CLLocation]){
guard let currentLocation = manager.location else {
print("location is nil")
return
}
print("locations =\(currentLocation.coordinate)")
for i in self.shopArray.indices{
// let currentLocation : CLLocation = CLLocation(latitude: localValue.latitude, longitude: localValue.longitude)
let shopLocation : CLLocation = CLLocation(latitude: self.shopArray[i].latitude, longitude: self.shopArray[i].longitude)
let distance = shopLocation.distance(from: currentLocation)
self.shopArray[i].distance = distance //<-
}
self.shopArray.sort{$0.distance ?? 0.0 < $1.distance ?? 0.0}
self.cafeTableView.reloadData()
}
}
}
}
}
}
一部抜粋することによりわかりにくくなってしまっていたのでコード全文も掲載いたします。
ご迷惑をおかけして申し訳ありません。(いくつかのご指摘をいただき修正が加えてあるため、上のコードとは相違があります。)
protocol DocumentSerializeable {
init?(dictionary:[String:Any])
}
struct Shop {
var title: String
var recomendation: String
var tel: String
var station: String
var latitude: Double
var longitude: Double
var price: Int
var img: String
var distance: CLLocationDistance?
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,UISearchBarDelegate,CLLocationManagerDelegate{
@IBOutlet var cafeTableView: UITableView!
var shopArray = [Shop]()
var db: Firestore!
private var mySearchBar: UISearchBar!
private weak var refreshControl: UIRefreshControl!
var locationManager: CLLocationManager!
var currentLocation: CLLocation!
//検索結果が入る配列
private var searchResult = [String]()
private var myItems = [String]()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
loadData()
let myNavBar = UINavigationBar()
//大きさの指定
myNavBar.frame = CGRect(x: 0, y: UIApplication.shared.statusBarFrame.height, width: self.view.frame.size.width, height: 44)
mySearchBar = UISearchBar()
//デリゲートを設定
mySearchBar.delegate = self
//大きさの指定
mySearchBar.frame = CGRect(x: 0, y: UIApplication.shared.statusBarFrame.height, width: self.view.frame.size.width, height: 44)
//キャンセルボタンの追加
mySearchBar.showsCancelButton = true
mySearchBar.placeholder = "カフェ名を入力してください"
cafeTableView.tableHeaderView = mySearchBar
cafeTableView.contentOffset = CGPoint(x: 0,y :44)
self.cafeTableView.delegate = self
self.cafeTableView.dataSource = self
getTitle()
initializePullToRefresh()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
refresh()
}
func loadData() {
let db = Firestore.firestore()
let shopsRef = db.collection("shops")
shopsRef.getDocuments() { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = snapshot{
for document in snapshot.documents {
print(document.data())
let data = document.data()
let title = data["title"] as? String ?? ""
let recomendation = data["recomendation"] as? String ?? ""
let tel = data["tel"] as? String ?? ""
let station = data["station"] as? String ?? ""
let latitude = data["latitude"] as? Double ?? Double()
let longitude = data["longitude"] as? Double ?? Double()
let price = data["price"] as? Int ?? Int()
let img = data["img"] as? String ?? ""
let distance = data["distance"] as? CLLocationDistance ?? Double()
let newShop = Shop(title: title, recomendation: recomendation, tel: tel, station: station, latitude: latitude, longitude: longitude, price: price, img: img, distance: distance)
self.shopArray.append(newShop)
self.cafeTableView.reloadData()
}
}
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLOcations locations: [CLLocation]) {
guard let currentLocation = manager.location else {
print("location is nil")
return
}
print("locations =\(currentLocation.coordinate)")
for i in shopArray.indices {
let shopLocation : CLLocation = CLLocation(latitude: shopArray[i].latitude, longitude: shopArray[i].longitude)
let distance = shopLocation.distance(from: currentLocation)
shopArray[i].distance = distance
}
//`locationManager(_:didUpdateLOcations:)`のように非同期に呼び出されるメソッドの中で
//UIスレッドで実行する必要のある処理は`DispatchQueue.main.async {...}`の中に書く
DispatchQueue.main.async {
//`distance`プロパティの値(nilなら0.0に置き換え)でソートする
//(SwiftのArrayはスレッドセーフではないので、ソートもUIスレッドの中で行う)
self.shopArray.sort {$0.distance ?? 0.0 < $1.distance ?? 0.0}
//`UITableView`の`realod()`と言ったUI操作はUIスレッドで実行するのが必須
self.cafeTableView.reloadData()
}
}
func getTitle(){
let db = Firestore.firestore()
db.collection("shops").whereField("title", isEqualTo: true)
.getDocuments() { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = snapshot{
for document in snapshot.documents {
print("\(document.documentID) => \(document.data())")
let data = document.data()
let title = data["title"] as? String ?? ""
self.myItems.append(title)
}
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func searchTapped(_ sender: Any) {
if cafeTableView.contentOffset == CGPoint(x: 0,y :0) {
cafeTableView.contentOffset = CGPoint(x: 0,y :44)
self.view.endEditing(true)
}else{
cafeTableView.contentOffset = CGPoint(x: 0,y :0)
self.view.endEditing(true)
}
}
//MARK: - 渡された文字列を含む要素を検索し、テーブルビューを再表示する
func searchItems(searchText: String){
//要素を検索する
if searchText != "" {
searchResult = myItems.filter { myItem in
return (myItem).contains(searchText)
}
}else{
//渡された文字列が空の場合は全てを表示
searchResult = myItems
}
cafeTableView.reloadData()
}
// MARK: - SearchBarのデリゲードメソッドたち
//MARK: テキストが変更される毎に呼ばれる
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//検索する
searchItems(searchText: searchText)
}
//MARK: キャンセルボタンが押されると呼ばれる
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
mySearchBar.text = ""
self.view.endEditing(true)
searchResult = myItems
//tableViewを再読み込みする
cafeTableView.reloadData()
}
//MARK: Searchボタンが押されると呼ばれる
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.view.endEditing(true)
//検索する
searchItems(searchText: mySearchBar.text! as String)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.shopArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ShopListTableViewCell", for: indexPath)
let shop = shopArray[indexPath.row]
let img = UIImage(named: "\(shop.img).jpg")
cafeTableView.separatorColor = UIColor.white
// Tag番号 1 で UIImageView インスタンスの生成
let imageView = cell.viewWithTag(1) as! UIImageView
imageView.image = img
// Tag番号 2 で UILabel インスタンスの生成
let shopName = cell.viewWithTag(2) as! UILabel
shopName.text = "\(shop.title)"
let placeName = cell.viewWithTag(3) as! UILabel
placeName.text = "\(shop.station)"
let distanceLabel = cell.viewWithTag(4) as! UILabel
distanceLabel.text = "\(String(describing: shop.distance))"
self.view.bringSubview(toFront: imageView)
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ table: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
return 135.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let DvC = storyBoard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
let shop = shopArray[indexPath.row]
DvC.getName = "\(shop.title)"
DvC.getImage = UIImage(named: "\(shop.img).jpg")!
DvC.getNumber = "\(shop.tel)"
DvC.getRecomend = "\(shop.recomendation)"
DvC.getPrice = "\(shop.price)"
self.navigationController?.pushViewController(DvC, animated: true)
}
@IBAction func getInfo(_ sender: Any) {
print("\(shopArray)")
print("\(currentLocation)")
}
// MARK: - Pull to Refresh
private func initializePullToRefresh() {
let control = UIRefreshControl()
control.addTarget(self, action: #selector(onPullToRefresh(_:)), for: .valueChanged)
cafeTableView.addSubview(control)
refreshControl = control
}
@objc private func onPullToRefresh(_ sender: AnyObject) {
refresh()
}
private func stopPullToRefresh() {
if refreshControl.isRefreshing {
refreshControl.endRefreshing()
}
}
// MARK: - Data Flow
private func refresh() {
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 1.0)
DispatchQueue.main.async {
self.completeRefresh()
}
}
}
private func completeRefresh() {
stopPullToRefresh()
cafeTableView.reloadData()
}