补充上节课的一点内容:
拖拽一个Bar Button Item到导航栏的左边插槽。在属性检查器中设置System Item为Cancel,并且将它和cancel动作方法关联起来。
再拖拽一个Bar Button Item到右边插槽。设置Style和System Item为Done,并且将它和done动作方法关联起来。
制作cell
这个table view中包含三个部分:
1、文本描述和分类,用户可以选择一个分类,并且自己编辑描述。
2、照片,这个cell初始的时候叫做Add Photo,一旦用户点击了它就可以选择一张照片上去。
3、经纬度、地址和日期,这些信息是只读的。
打开故事模版。选择table view并且打开它的属性检查器,将Sections从1改为3。
当你这样做了以后,第一个section的三行被复制了两遍,现在屏幕上可以看到有9行,这不是你想要的效果,所以你要删掉其中的一些。第一个section需要有两行,中的只要1行就够了,而最后一个section需要4行。
从第一个section中删除一行,删除的时候确保你选择的是table view而不是Content View。
从中间的section中删除掉两行。
选择最后一个section,从略缩面板中比较容易操作,见下图,打开属性检查器,并且设置Rows为4。
(还有一个方法是从元件库中拖一个Table View Cell上去)
第一个section中的第二个cell,和第三个section中的1,2,4行cell使用标准cell风格。
选择这些cell,并且设置它们的Style为Right Detail。
这些标准cell中的labels是UILabel对象,你可以选择它们并且改变它们的属性。
将这些label分别命名为:Category、Latitude,Longitude与Date。
如果在你输入的时候,这些标签的位置发生了变化,你可以先将cell style修改为Left Detail,然后再修改为Right Detail。
拖拽一个新的Label到第二个section的cell中,对这个cell,你不能使用标准cell风格,你需要自己设计它。将这个label命名为Add Photo。(稍后你还要添加一个image view到这个cell中)。
确保字体为System,大小为17,这样它就和其他cell的字体保持一致了。你还可以使用菜单Editor->Size to Fit Content来保持它的大小为最佳尺寸。
将这个label的位置调整为X:15(在尺寸检查器中)并且在菜单Editor->Align->Vertically中选择vertically centered(居中垂直),如果这个菜单是灰色的,就取消选定label,再次选中它,多试几次。
完成后应该是这样的:
⚠️:你对多个cell进行大量同一操作时,可以一次性选中它们,然后只操作一次。这样会节省很多时间。在略缩面板中按住command键就可以多选了(但是不能在故事模版中这样多选操作,因为Xcode不支持在不同的section间多选),不幸的是,有些操作不支持这样批量操作,它们会是灰色的,对于这种操作,你还是得一个个的做。
这些cell中只有Category和Add Photo是可以被用户点击的,批量选择它俩以外的所有cell,然后在属性检查器中将Selection设置为None。
选择Category和Add Photo cell,设置它们的Accessory为Disclosure Indicator。
第三个section中的第三个cell(就是还空着的那一个),是为地址信息准备的。它看起来会和“Right Detail”很像,但是它是一个你自定义的cell。
拖拽一个label上去,并且命名为Address,并且设置其位置为X:15,Y:11。
再拖拽一个cell进去,命名为Detail,放在右边,设置位置为X:261,Y:11。
在属性检查器中将detail标签的Alignment设置为right-aligned(右对齐)。
detail标签是特别的。大多数街道信息的长度都无法在一行中容纳,所以你需要将这个label配置为可以容纳多行数据的模式。这需要大量的编程工作以及属性设置工作。
选择这个detail标签,在属性检查器中设置Lines为0,Line Break为Word Wrap。当Lines为0时,这个label可以以垂直的方式重新调整你输入的文本,这正是你需要的。
目前为止,最顶端的那个cell还是空的,选择这个cell,在尺寸检查器中设置它的Row Height为88。注意一下,你需要先勾选旁边的Custom选框后才能选择改变这个值。
我选择高度88的原因是,iOS中的大部分组件的高度是44,比如导航栏,标准的cell等,所以刚好为44的倍数会显得好看一些。
拖拽一个Text View到这个cell中去,并且设置它的位置为X:15,Y:10,Width:290,Height:68。字体为:System 17。
还是选中这个Text View,在尺寸检查器中奖Autoresizing设置为下面这个样子:
这样这个text view就可以适应6s,7和各种plus设备了。
对于界面的布局,我们还有最后一件事要做,因为最顶端的这个cell上没有label用于描述它的作用,并且text view初始化的时候是空的,所以用户肯定会很迷惑,这里到底是做什么用的。
而这里确实也没什么空间可以用来再放一个label进去了,但是就像我们为其他行所做的那样,我们在该部分添加一个标题。 table view的section可以有页眉和页脚,我们可以利用这个工具。
选择第一个table view section(蓝色立方体图标的那种),在属性检查器中设置Header为:Description。
界面设计就彻底完成了。Tag Location界面最终看起来是这个样子:
把每个detail标签和text view和它们对应的outlet链接起来,它们的对应关系是一目了然的。
完成后,这个视图控制器的链接检查器看起来会是这个样子:
运行app,确保一切正常。
把地址信息放到新的这个界面
打开LocationDetailsViewController.swift,添加两条新的属性:
var coordinate = CLLocationCoordinate2D(latitude: 0,longitude: 0)
var placemark: CLPlacemark?
你之前见过CLPlacemark对象。它包含地址解析后的街道地址信息,城市名称等等。它是一个可选型,因为不能保证地址解析一定会得到一个地址。
CLLocationCoordinate2D是新来的。它包含你从location manager中接收到的CLLocation对象。coordinate不是一个可选型,所以它必须有一个初始值。
练习:为什么coordinate不是可选型呢?
答案:如果GPS坐标没有找到,那么你根本无法点击Tag Location按钮,所以只要你能够点击这个按钮打开Tag Location界面,就说明GPS坐标一定是存在的。
在Current Location界面到Tag Location界面的专场过程中,你将会给这两个新的属性赋值,然后Tag Location界面就可以把这些值展示到标签中了。
也许你已经发现了,Xcode对你刚才添加的两行非常不满。它说到:“Use of unresolved indentifier CLLocationCoordinate2D and CLPlacemark”。这意思是Xcode目前对这些类型一无所知。
因为它们不属于Core Location的一部分,你必须先导入包含它们的框架。
在import UIKit下面加上这一行:
import CoreLocation
现在Xcode的报错信息应该消失了,如果没有你可以使用command+B,重编一下。
插播一个话题
结构(struct)
我刚才说了,CLLocationCoordinate2D是一个对象,但是它和你曾经见过的对象有些不同,它不是来自于一个类(class),而是来自一个结构(struct)。
结构和类很像,但是功能比类要差一点。结构也拥有属性和方法,但是它们无法互相继承。
CLLocationCoordinate2D的声明语句是下面这个样子:
struct CLLocationCoordinate2D {
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
}
这个结构有两个字段(其实就是属性,本书原著经常换名词,我也是有点晕,以后我们就都叫属性了),latitude和longitude,着两个属性的数据类型都是CLLocationDegress,这是Double的一个别名:
typealias CLLocationDegrees = Double
Double类型是一种语言中内建的原始类型。它和Float很像,但是精度更广。
不要让这些同义词迷惑了你;CLLocationCoordinate2D基本上就是这样:
struct CLLocationCoordinate2D {
var latitude: Double
var longitude: Double
}
Core Location中用CLLocationDegrees代替Double的原因是“CL Location Degrees”可以更清晰的让你明白它的意图,它是用来存储来自Core Location中的location的精度的。
UIKit以及其他iOS框架中都经常会使用结构。比如CGPoint和CGRect。事实上,Array(数组)和Dictionary(字典)也是结构。
注意力回到我们的新属性上,当用户点击Tag Location按钮时,你需要填满这些属性。
切换到CurrentLocationViewController.swift,添加一个prepare-for-segue方法:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TagLocation" {
let navigationController = segue.destination as! UINavigationController
let controller = navigationController.topViewController as! LocationDetailsViewController
controller.coordinate = location!.coordinate
controller.placemark = placemark
}
}
注意一下,这个方法必须放在 //MARK: - CLLocationManagerDelegate这行注释的前面。
之前我们已经讲过prepare-for-segue方法的实现原理了。你使用了角色扮演的魔法获取了合适的目标视图控制器类型并且设置了它的属性。现在,只要执行转场,坐标和地址信息就会被Tag Location界面获取到。
因为location是个可选型,所以你使用它之前要对它进行解包。这里我们使用强制解包是非常安全的,因为只有location存在时,这个转场才能被执行。
placemark也是一个可选型,同时LocationDetailsViewController中的placemark也是可选型,所以你不用进行任何处理。你可以直接把一个可选型传递给另一个可选型。
把这些信息展示在屏幕上的时机就是在viewDidLoad()中了。
打开LocationDetailsViewController.swift,添加viewDidLoad()方法:
override func viewDidLoad() {
super.viewDidLoad()
descriptionTextView.text = ""
categoryLable.text = ""
latitudeLabel.text = String(format: "%.8f",coordinate.latitude)
longitudeLabel.text = String(format: "%.8f",coordinate.longitude)
if let placemark = placemark {
addressLabel.text = string(from: placemark)
} else {
addressLabel.text = "No Address Found"
}
dateLabel.text = format(date:Date())
}
这段代码的作用就是把所有信息放入对应的标签里。它使用了两个辅助方法,稍后你会定义它们:string(form)用于将CLPlacemark对象格式化为string,format(date)用于处理Date对象。
在viewDidLoad()方法下添加string(from)方法:
func string(from placemark: CLPlacemark) -> String {
var text = ""
if let s = placemark.subThoroughfare {
text += s + " "
}
if let s = placemark.thoroughfare {
text += s + ", "
}
if let s = placemark.locality {
text += s + " "
}
if let s = placemark.administrativeArea {
text += s + ", "
}
if let s = placemark.postalCode {
text += s + ", "
}
if let s = placemark.country {
text += s
}
return text
}
它看起来非常熟悉。在主界面上你处理placemark时就用了这种方法,只是现在这里还多了一个county属性。
下节课我们开始处理Date对象。(^^)