Xamarin.iOS 中的地图
地图是所有新式移动操作系统的常见功能。 iOS 通过 Map Kit 框架原生提供地图支持。 借助 Map Kit,应用可以轻松添加丰富的交互式地图。 可以通过多种方式自定义这些地图,例如添加注释以标记地图上的位置,以及叠加任意形状的图形。 Map Kit 甚至内置支持显示设备的当前位置。
添加地图
将地图添加到应用程序是通过将 MKMapView
实例添加到视图层次结构来实现的,如下所示:
// map is an MKMapView declared as a class variable
map = new MKMapView (UIScreen.MainScreen.Bounds);
View = map;
MKMapView
是显示地图的 UIView
子类。 只需使用上面的代码添加地图即可生成交互式地图:
地图样式
MKMapView
支持 3 种不同样式的地图。 若要应用地图样式,只需将 MapType
属性设置为 MKMapType
枚举中的值即可:
map.MapType = MKMapType.Standard; //road map
map.MapType = MKMapType.Satellite;
map.MapType = MKMapType.Hybrid;
以下屏幕截图显示了可用的不同地图样式:
平移和缩放
MKMapView
包括对地图交互功能的支持,例如:
- 通过捏合手势进行缩放
- 通过平移手势进行平移
只需设置 MKMapView
实例的 ZoomEnabled
和 ScrollEnabled
属性即可启用或禁用这些功能,其中两者的默认值为 true。 例如,若要显示静态地图,只需将相应的属性设置为 false:
map.ZoomEnabled = false;
map.ScrollEnabled = false;
用户位置
除了用户交互之外,MKMapView
还内置支持显示设备位置。 它使用 Core Location 框架来实现此目的。 在访问用户的位置之前,必须先提示用户。 为此,请创建 CLLocationManager
的实例并调用 RequestWhenInUseAuthorization
。
CLLocationManager locationManager = new CLLocationManager();
locationManager.RequestWhenInUseAuthorization();
//locationManager.RequestAlwaysAuthorization(); //requests permission for access to location data while running in the background
请注意,在 8.0 之前的 iOS 版本中,尝试调用 RequestWhenInUseAuthorization
会导致错误。 如果你想要支持 8 之前的版本,请务必在调用之前检查 iOS 版本。
访问用户位置还需要对 Info.plist 进行修改。 应设置如下与位置数据相关的键:
- NSLocationWhenInUseUsageDescription - 用于用户在和你的应用互动时访问用户的位置。
- NSLocationAlwaysUsageDescription - 用于应用在后台访问用户的位置时。
可以通过打开 Info.plist 并选择编辑器底部的“源”来添加这些键。
更新 Info.plist 并提示用户授予访问其位置的权限后,可以通过将 ShowsUserLocation
属性设置为 true,在地图上显示用户的位置:
map.ShowsUserLocation = true;
批注
MKMapView
还支持在地图上显示图像(称为注释)。 这些可以是自定义图像或系统定义的各种颜色的图钉。 例如,以下屏幕截图显示了带有图钉和自定义图像的地图:
添加注释
注释本身有两部分:
MKAnnotation
对象,其中包含有关注释的模型数据,例如注释的标题和位置。MKAnnotationView
,其中包含要显示的图像以及用户点击注释时显示的(可选)标注。
Map Kit 使用 iOS 委托模式向地图添加注释,其中 MKMapView
的 Delegate
属性设置为 MKMapViewDelegate
的实例。 该委托的实现负责返回注释的 MKAnnotationView
。
若要添加注释,首先请通过在 MKMapView
实例上调用 AddAnnotations
来添加注释:
// add an annotation
map.AddAnnotations (new MKPointAnnotation (){
Title="MyAnnotation",
Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});
当注释的位置在地图上可见时,MKMapView
将调用其委托的 GetViewForAnnotation
方法来显示 MKAnnotationView
。
例如,以下代码返回系统提供的 MKPinAnnotationView
:
string pId = "PinAnnotation";
public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation)
{
if (annotation is MKUserLocation)
return null;
// create pin annotation view
MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);
if (pinView == null)
pinView = new MKPinAnnotationView (annotation, pId);
((MKPinAnnotationView)pinView).PinColor = MKPinAnnotationColor.Red;
pinView.CanShowCallout = true;
return pinView;
}
重用注释
为了节省内存,MKMapView
允许组建注释视图池以供重用,这与重用表单元格的方式类似。 通过调用 DequeueReusableAnnotation
从池中获取注释视图:
MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);
显示标注
如前所述,注释可以选择性地显示标注。 若要显示标注,只需在 MKAnnotationView
上将 CanShowCallout
设置为 true。 这会导致点击注释时显示注释的标题,如下所示:
自定义标注
还可以自定义标注以显示左右附件视图,如下所示:
pinView.RightCalloutAccessoryView = UIButton.FromType (UIButtonType.DetailDisclosure);
pinView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile ("monkey.png"));
此代码会生成以下标注:
若要处理用户点击正确附件的场景,只需在 MKMapViewDelegate
中实现 CalloutAccessoryControlTapped
方法即可:
public override void CalloutAccessoryControlTapped (MKMapView mapView, MKAnnotationView view, UIControl control)
{
...
}
覆盖
在地图上对图形进行分层的另一种方法是使用叠加层。 叠加层支持绘制在地图缩放时随之缩放的图形内容。 iOS 提供对多种类型的叠加层的支持,包括:
- 多边形 - 通常用于突出显示地图上的某些区域。
- 折线- 显示路线时经常出现。
- 圆圈 - 用于突出显示地图的圆形区域。
此外,还可以创建自定义叠加层,以使用精细的自定义绘图代码演示任意几何图形。 例如,气象雷达是自定义叠加层的适当候选项。
添加叠加层
与注释类似,添加叠加层涉及两个部分:
- 为叠加层创建模型对象并将其添加到
MKMapView
。 - 在
MKMapViewDelegate
中为叠加层创建视图。
叠加层的模型可以是任何 MKShape
子类。 Xamarin.iOS 分别通过 MKPolygon
、MKPolyline
和 MKCircle
类包括多边形、折线和圆的 MKShape
子类。
例如,以下代码用于添加 MKCircle
:
var circleOverlay = MKCircle.Circle (mapCenter, 1000);
map.AddOverlay (circleOverlay);
叠加层的视图是一个 MKOverlayView
实例,由 MKMapViewDelegate
中的 GetViewForOverlay
返回。 每个 MKShape
都有一个相应的 MKOverlayView
,它知道如何显示给定的形状。 对于 MKPolygon
,提供了 MKPolygonView
。 同样,MKPolyline
对应于 MKPolylineView
,而对于 MKCircle
,则提供了 MKCircleView
。
例如,以下代码为 MKCircle
返回 MKCircleView
:
public override MKOverlayView GetViewForOverlay (MKMapView mapView, NSObject overlay)
{
var circleOverlay = overlay as MKCircle;
var circleView = new MKCircleView (circleOverlay);
circleView.FillColor = UIColor.Blue;
return circleView;
}
这会在地图上显示一个圆,如下所示:
本地搜索
iOS 包含带有 Map Kit 的本地搜索 API,它允许异步搜索指定地理区域中的兴趣点。
若要执行本地搜索,应用程序必须遵循以下步骤:
- 创建
MKLocalSearchRequest
对象。 - 从
MKLocalSearchRequest
创建一个MKLocalSearch
对象。 - 对
MKLocalSearch
对象调用Start
方法。 - 检索回调中的
MKLocalSearchResponse
对象。
本地搜索 API 本身不提供用户界面。 它甚至不需要使用地图。 但是,若要实际使用本地搜索,应用程序需要提供某种方法来指定搜索查询并显示结果。 此外,由于结果将包含位置数据,因此在地图上显示它们通常是有意义的。
添加本地搜索 UI
接受搜索输入的一种方法是使用 UISearchBar
,它由 UISearchController
提供,并将在表中显示结果。
以下代码将 UISearchController
(具有搜索栏属性)添加到 MapViewController
的 ViewDidLoad
方法中:
//Creates an instance of a custom View Controller that holds the results
var searchResultsController = new SearchResultsViewController (map);
//Creates a search controller updater
var searchUpdater = new SearchResultsUpdator ();
searchUpdater.UpdateSearchResults += searchResultsController.Search;
//add the search controller
searchController = new UISearchController (searchResultsController) {
SearchResultsUpdater = searchUpdater
};
//format the search bar
searchController.SearchBar.SizeToFit ();
searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Minimal;
searchController.SearchBar.Placeholder = "Enter a search query";
//the search bar is contained in the navigation bar, so it should be visible
searchController.HidesNavigationBarDuringPresentation = false;
//Ensure the searchResultsController is presented in the current View Controller
DefinesPresentationContext = true;
//Set the search bar in the navigation bar
NavigationItem.TitleView = searchController.SearchBar;
请注意,你需要负责将搜索栏对象合并到用户界面中。 在此示例中,我们将其分配到导航栏的 TitleView,但如果在应用中不使用导航控制器,则必须找到另一个位置来显示它。
在此代码片段中,我们创建了另一个自定义视图控制器 – searchResultsController
– 用于显示搜索结果,然后使用该对象来创建搜索控制器对象。 我们还创建了一个新的搜索更新程序,当用户与搜索栏交互时,该更新程序就会激活。 它会接收有关每次按键搜索的通知,并负责更新 UI。
本指南稍后将介绍如何实现 searchResultsController
和 searchResultsUpdater
。
这会导致地图上显示一个搜索栏,如下所示:
显示搜索结果
若要显示搜索结果,我们需要创建一个自定义视图控制器;通常是 UITableViewController
。 如上所示,searchResultsController
在创建时会传递给 searchController
的构造函数。
以下代码示例演示如何创建此自定义视图控制器:
public class SearchResultsViewController : UITableViewController
{
static readonly string mapItemCellId = "mapItemCellId";
MKMapView map;
public List<MKMapItem> MapItems { get; set; }
public SearchResultsViewController (MKMapView map)
{
this.map = map;
MapItems = new List<MKMapItem> ();
}
public override nint RowsInSection (UITableView tableView, nint section)
{
return MapItems.Count;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (mapItemCellId);
if (cell == null)
cell = new UITableViewCell ();
cell.TextLabel.Text = MapItems [indexPath.Row].Name;
return cell;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
// add item to map
CLLocationCoordinate2D coord = MapItems [indexPath.Row].Placemark.Location.Coordinate;
map.AddAnnotations (new MKPointAnnotation () {
Title = MapItems [indexPath.Row].Name,
Coordinate = coord
});
map.SetCenterCoordinate (coord, true);
DismissViewController (false, null);
}
public void Search (string forSearchString)
{
// create search request
var searchRequest = new MKLocalSearchRequest ();
searchRequest.NaturalLanguageQuery = forSearchString;
searchRequest.Region = new MKCoordinateRegion (map.UserLocation.Coordinate, new MKCoordinateSpan (0.25, 0.25));
// perform search
var localSearch = new MKLocalSearch (searchRequest);
localSearch.Start (delegate (MKLocalSearchResponse response, NSError error) {
if (response != null && error == null) {
this.MapItems = response.MapItems.ToList ();
this.TableView.ReloadData ();
} else {
Console.WriteLine ("local search error: {0}", error);
}
});
}
}
更新搜索结果
SearchResultsUpdater
充当 searchController
的搜索栏和搜索结果之间的中介。
在此示例中,我们必须首先在 SearchResultsViewController
中创建搜索方法。 为此,我们必须创建一个 MKLocalSearch
对象并使用它来发出对 MKLocalSearchRequest
的搜索,结果在传递给 MKLocalSearch
对象的 Start
方法的回调中检索。 然后,结果在包含 MKMapItem
对象数组的 MKLocalSearchResponse
对象中返回:
public void Search (string forSearchString)
{
// create search request
var searchRequest = new MKLocalSearchRequest ();
searchRequest.NaturalLanguageQuery = forSearchString;
searchRequest.Region = new MKCoordinateRegion (map.UserLocation.Coordinate, new MKCoordinateSpan (0.25, 0.25));
// perform search
var localSearch = new MKLocalSearch (searchRequest);
localSearch.Start (delegate (MKLocalSearchResponse response, NSError error) {
if (response != null && error == null) {
this.MapItems = response.MapItems.ToList ();
this.TableView.ReloadData ();
} else {
Console.WriteLine ("local search error: {0}", error);
}
});
}
然后,在 MapViewController
中,我们将创建 UISearchResultsUpdating
的自定义实现,将其分配到添加本地搜索 UI 部分中 searchController
的 SearchResultsUpdater
属性:
public class SearchResultsUpdator : UISearchResultsUpdating
{
public event Action<string> UpdateSearchResults = delegate {};
public override void UpdateSearchResultsForSearchController (UISearchController searchController)
{
this.UpdateSearchResults (searchController.SearchBar.Text);
}
}
上面的实现在从结果中选择一个项目时向地图添加注释,如下所示:
重要
UISearchController
是在 iOS 8 中实现的。 如果你希望支持低于此版本的设备,则需要使用 UISearchDisplayController
。
总结
本文介绍了适用于 iOS 的映射工具包框架。 首先,其中介绍了 MKMapView
类如何允许将交互式地图包含在应用中。 然后演示了如何使用注释和叠加层进一步自定义地图。 最后,探讨了 iOS 6.1 中添加到 Map Kit 中的本地搜索功能,并介绍了如何对兴趣点执行基于位置的查询并将其添加到地图中。