首页 iOS.& Swift Books iOS.Apprentice

28
位置标签 由Eli Ganim撰写

您已经设置了数据模型,并给出了应用程序能够将新位置保存到日期存储。接下来,您将在第二个选项卡中的表视图中显示这些已保存的位置。

完成的位置屏幕将如下所示:

位置屏幕
位置屏幕

本章涵盖以下内容:

  • 位置标签:设置第二个选项卡以显示已保存的位置列表。
  • 创建自定义表视图单元子类:创建自定义表视图单元子类以处理显示位置信息。
  • 编辑位置:添加功能以允许在位置列表中编辑项目。
  • 使用nsfetchedresultscontroller.: How do you use NSFetchedResultsController to fetch data from your Core Data store?
  • 删除位置:添加UI删除位置的能力,从而从核心数据存储中删除它们。
  • 表视图部分:使用内置核心数据功能来添加基于位置类别显示单独部分的功能。

位置标签

➤打开故事板编辑器并删除 第二场景。这是项目模板的剩余,您不需要它。

➤拖动一个新的 导航控制器 到画布上 - 它具有连接到它的表视图控制器,这很好。你将在一秒钟内使用。

控制阻力 从Tab条控制器到此新导航控制器并选择 关系segue - 查看控制器。这将导航控制器添加到Tab栏中。

➤导航控制器现在有一个 标签条项 那是名为“项目”。重命名它 地点.

➤双击新表视图控制器的导航栏(连接到新导航控制器的一个)并将标题更改为 地点。 (如果Xcode为您提供问题,请使用导航项上的属性检查器。)

故事板现在看起来像这样:

添加位置屏幕后的故事板
添加位置屏幕后的故事板

➤运行应用程序并激活“位置”选项卡。它没有显示任何有用的东西:

第二个选项卡中的位置屏幕
第二个选项卡中的位置屏幕

设计表视图单元格

在您在表中显示任何数据之前,首先必须设计原型单元格。

原型细胞
kzu ctufikvmi boyt.

基本表视图控制器

让我们编写视图控制器的代码。你现在已经看到了表视图控制器,所以这应该很容易。

import UIKit
import CoreData
import CoreLocation

class LocationsViewController: UITableViewController {
  var managedObjectContext: NSManagedObjectContext!

  // MARK: - Table View Delegates
  override func tableView(_ tableView: UITableView, 
        numberOfRowsInSection section: Int) -> Int {
    return 1
  }

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

    let descriptionLabel = cell.viewWithTag(100) as! UILabel
    descriptionLabel.text = "If you can see this"

    let addressLabel = cell.viewWithTag(101) as! UILabel
    addressLabel.text = "Then it works!"

    return cell
  }
}
表查看假数据
NKU Yomva Nuam Gown Vaye Tike

从数据存储显示位置

➤运行应用程序并标记少数位置。如果数据存储中没有数据,那么该应用程序没有太多才能显示......

var locations = [Location]()
override func viewDidLoad() {
  super.viewDidLoad()
  // 1
  let fetchRequest = NSFetchRequest<Location>()
  // 2
  let entity = Location.entity()
  fetchRequest.entity = entity
  // 3
  let sortDescriptor = NSSortDescriptor(key: "date", 
                                  ascending: true)
  fetchRequest.sortDescriptors = [sortDescriptor]
  do {
    // 4
    locations = try managedObjectContext.fetch(fetchRequest)
  } catch {
    fatalCoreDataError(error)
  }
}
override func tableView(_ tableView: UITableView, 
      numberOfRowsInSection section: Int) -> Int {
  return locations.count
}
override func tableView(_ tableView: UITableView,
             cellForRowAt indexPath: IndexPath) -> 
             UITableViewCell {
  let cell = tableView.dequeueReusableCell(
                       withIdentifier: "LocationCell", 
                                  for: indexPath)

  let location = locations[indexPath.row]

  let descriptionLabel = cell.viewWithTag(100) as! UILabel
  descriptionLabel.text = location.locationDescription

  let addressLabel = cell.viewWithTag(101) as! UILabel
  if let placemark = location.placemark {
    var text = ""
    if let s = placemark.subThoroughfare {
      text += s + " "
    }
    if let s = placemark.thoroughfare {
      text += s + ", "
    }
    if let s = placemark.locality {
      text += s
    }
    addressLabel.text = text
  } else {
    addressLabel.text = ""
  }
  return cell
}
fatal error: unexpectedly found nil while unwrapping an Optional value
if let tabViewControllers = tabController.viewControllers {
  // First tab
  var navController = tabViewControllers[0]            
                      as! UINavigationController
  let controller1 = navController.viewControllers.first 
                    as! CurrentLocationViewController
  controller1.managedObjectContext = managedObjectContext
  // Second tab
  navController = tabViewControllers[1] 
                  as! UINavigationController
  let controller2 = navController.viewControllers.first 
                    as! LocationsViewController
  controller2.managedObjectContext = managedObjectContext  
}
位置列表
BSA Heyq UY Lojuciegw

创建自定义表视图单元组

Using viewWithTag(_:) to find the labels from the table view cell works, but it doesn’t look very object-oriented to me.

@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var addressLabel: UILabel!
override func tableView(_ tableView: UITableView,
             cellForRowAt indexPath: IndexPath) -> 
             UITableViewCell {
  let cell = tableView.dequeueReusableCell(
                       withIdentifier: "LocationCell", 
                       for: indexPath) as! LocationCell

  let location = locations[indexPath.row]
  cell.configure(for: location)

  return cell
}
// MARK:- Helper Method
func configure(for location: Location) {
  if location.locationDescription.isEmpty {
    descriptionLabel.text = "(No Description)"
  } else {
    descriptionLabel.text = location.locationDescription
  }

  if let placemark = location.placemark {
    var text = ""
    if let s = placemark.subThoroughfare {
      text += s + " "
    }
    if let s = placemark.thoroughfare {
      text += s + ", "
    }
    if let s = placemark.locality {
      text += s
    }
    addressLabel.text = text
  } else {
    addressLabel.text = String(format:
      "Lat: %.8f, Long: %.8f", location.latitude, 
                               location.longitude)
  }
}

编辑位置

You will now connect the 地点ViewController to the Location Details screen, so that when you tap a row in the table, it lets you edit that location’s description and category.

创建编辑segue.

➤转到故事板。从位置场景中选择原型单元格 控制阻力 到标记位置场景(这是位置细节屏幕)。添加A. 展示 选择segue并命名它 editleocation..

位置详细信息屏幕现在也连接到位置屏幕
QMI Larenoef Ceguigp JBKAUS AB QIS UJGE QUSGUBCEN KI GYI MILIBOONS LTQUEQ

// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue, 
                         sender: Any?) {
  if segue.identifier == "editleocation." {
    let controller = segue.destination 
                     as! LocationDetailsViewController
    controller.managedObjectContext = managedObjectContext

    if let indexPath = tableView.indexPath(for: sender 
                                         as! UITableViewCell) {
      let location = locations[indexPath.row]
      controller.locationToEdit = location
    }
  }
}

任何类型

The type of the sender parameter is Any. You have seen this type in a few places before. What is it?

设置编辑视图控制器

When editing an existing Location object, you have to do a few things differently in the LocationDetailsViewController. The title of the screen shouldn’t be “Tag Location” but “Edit Location.” You also must put the values from the existing Location object into the various cells.

var locationToEdit: Location?
var descriptionText = ""	
override func viewDidLoad() {
  super.viewDidLoad()
  if let location = locationToEdit {
    title= "Edit Location"
  }
  . . .
}
descriptionTextView.text = descriptionText
var locationToEdit: Location? {
  didSet {
    if let location = locationToEdit {
      descriptionText = location.locationDescription
      categoryName = location.category
      date = location.date
      coordinate = CLLocationCoordinate2DMake(
        location.latitude, location.longitude)
      placemark = location.placemark
    }
  }
}
编辑现有位置
izaxufcutokongiyr zojahoed

修复编辑屏幕

有两个问题可以解决:

@IBAction func done() {
  let hudView = HudView.hud(inView: . . .)

  let location: Location
  if let temp = locationToEdit {
    hudView.text = "Updated"
    location = temp
  } else {
    hudView.text = "Tagged"
    location = Location(context: managedObjectContext)
  }

  location.locationDescription = descriptionTextView.text
  . . .

使用nsfetchedresultscontroller.

由于您毫无疑问地意识到,表明IOS应用程序中的视图。很多时候您正在使用核心数据时,要从数据存储中获取对象并在表视图中显示它们。并且当这些对象更改时,您要以响应的情况为表视图执行实时更新,以显示对用户的更改。

lazy var fetchedResultsController: 
         NSFetchedResultsController<Location> = {
  let fetchRequest = NSFetchRequest<Location>()

  let entity = Location.entity()
  fetchRequest.entity = entity
  
  let sortDescriptor = NSSortDescriptor(key: "date", 
                                  ascending: true)
  fetchRequest.sortDescriptors = [sortDescriptor]
  
  fetchRequest.fetchBatchSize = 20
  
  let fetchedResultsController = NSFetchedResultsController(
            fetchRequest: fetchRequest, 
    managedObjectContext: self.managedObjectContext,
      sectionNameKeyPath: nil, cacheName: "地点")
  
  fetchedResultsController.delegate = self
  return fetchedResultsController
}()
fetchRequest.fetchBatchSize = 20
let fetchedResultsController = NSFetchedResultsController(
  fetchRequest: fetchRequest,
  managedObjectContext: self.managedObjectContext,
  sectionNameKeyPath: nil, cacheName: "地点")
override func viewDidLoad() {
  super.viewDidLoad()
  performFetch()
}

// MARK:- Helper methods
func performFetch() {
  do {
    try fetchedResultsController.performFetch()
  } catch {
    fatalCoreDataError(error)
  }
}
deinit {
  fetchedResultsController.delegate = nil
}
override func tableView(_ tableView: UITableView, 
      numberOfRowsInSection section: Int) -> Int {
  let sectionInfo = fetchedResultsController.sections![section] 
  return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, 
             cellForRowAt indexPath: IndexPath) -> 
             UITableViewCell {
  let cell = tableView.dequeueReusableCell(
        withIdentifier: "LocationCell", 
                   for: indexPath) as! LocationCell

  let location = fetchedResultsController.object(at: indexPath)
  cell.configure(for: location)
  
  return cell
}

使用Extensions组织代码

扩展名允许您将代码添加到现有类,而无需修改原始类源代码。当你延伸你说的时候,“这是一堆额外的方法,也需要进入那个类,”即使你没有写出原来的类开始。

// MARK:- NSFetchedResultsController Delegate Extension
extension LocationsViewController: 
          NSFetchedResultsControllerDelegate {
  
  func controllerWillChangeContent(_ controller: 
          NSFetchedResultsController<NSFetchRequestResult>) {
    print("*** controllerWillChangeContent")
    tableView.beginUpdates()
  }
  
  func controller(_ controller: 
          NSFetchedResultsController<NSFetchRequestResult>, 
          didChange anObject: Any, at indexPath: IndexPath?, 
          for type: NSFetchedResultsChangeType, 
          newIndexPath: IndexPath?) {

    switch type {
    case .insert:
      print("*** NSFetchedResultsChangeInsert (object)")
      tableView.insertRows(at: [newIndexPath!], with: .fade)
      
    case .delete:
      print("*** NSFetchedResultsChangeDelete (object)")
      tableView.deleteRows(at: [indexPath!], with: .fade)
      
    case .update:
      print("*** NSFetchedResultsChangeUpdate (object)")
      if let cell = tableView.cellForRow(at: indexPath!) 
                                      as? LocationCell {
        let location = controller.object(at: indexPath!) 
                       as! Location
        cell.configure(for: location)
      }

    case .move:
      print("*** NSFetchedResultsChangeMove (object)")
      tableView.deleteRows(at: [indexPath!], with: .fade)
      tableView.insertRows(at: [newIndexPath!], with: .fade)
    }
    
    @unknown default:
      fatalError("Unhandled switch case of NSFetchedResultsChangeType")
    }
  }
  
  func controller(_ controller: 
          NSFetchedResultsController<NSFetchRequestResult>, 
          didChange sectionInfo: NSFetchedResultsSectionInfo, 
          atSectionIndex sectionIndex: Int, 
          for type: NSFetchedResultsChangeType) {
    switch type {
    case .insert:
      print("*** NSFetchedResultsChangeInsert (section)")
      tableView.insertSections(IndexSet(integer: sectionIndex), 
                                           with: .fade)
    case .delete:
      print("*** NSFetchedResultsChangeDelete (section)")
      tableView.deleteSections(IndexSet(integer: sectionIndex),
                                           with: .fade)
    case .update:
      print("*** NSFetchedResultsChangeUpdate (section)")    
    case .move:
      print("*** NSFetchedResultsChangeMove (section)")
    }
    @unknown default:
      fatalError("Unhandled switch case of NSFetchedResultsChangeType")
    }
  }
  
  func controllerDidChangeContent(_ controller:
          NSFetchedResultsController<NSFetchRequestResult>) {
    print("*** controllerDidChangeContent")
    tableView.endUpdates()
  }
}
*** controllerWillChangeContent
*** NSFetchedResultsChangeUpdate (object)
*** controllerDidChangeContent
*** controllerWillChangeContent
*** NSFetchedResultsChangeInsert (object)
*** controllerDidChangeContent

“这不是一个错误,这是一个无证的功能”

有一个令人讨厌的核心数据错误,这是最后几个iOS版本的态度。以下是您如何重现它:

CoreData: FATAL ERROR: The persistent cache of section information does not match the current configuration.  You have illegally mutated the NSFetchedResultsController’s fetch request, its predicate, or its sort descriptor without either disabling caching or using +deleteCacheWithName:
NSFetchedResultsController<Location>.deleteCache(withName: "地点")
let _ = controller2.view

删除位置

Everyone makes mistakes. So, it’s likely that users will want to delete locations from their list at some point. This is a very easy feature to add: you just have to remove the Location object from the data store and the NSFetchedResultsController will make sure it gets dropped from the table — again, through its delegate methods.

override func tableView(_ tableView: UITableView, 
              commit editingStyle: UITableViewCell.EditingStyle, 
              forRowAt indexPath: IndexPath) {
  if editingStyle == .delete {
    let location = fetchedResultsController.object(at: 
                                             indexPath)
    managedObjectContext.delete(location)
    do {
      try managedObjectContext.save()
    } catch {
      fatalCoreDataError(error)
    }
  }
}
滑动以从表中删除行
XXEZU RE NOGABE WANP YZOM HFE DEQRO

navigationItem.rightBarButtonItem = editButtonItem
编辑模式下的表视图
Zne Wugpa Boiq Ih ozaw Lewe

表视图部分

The Location objects have a category field. It would be nice to group the locations by category in the table. The table view supports organizing rows into sections and each of these sections can have its own header. Putting your rows into sections is a lot of work if you’re doing it by hand, but NSFetchedResultsController practically gives you section support for free.

lazy var fetchedResultsController: . . . = {
  . . .
  let sort1 = NSSortDescriptor(key: "category", ascending: true)
  let sort2 = NSSortDescriptor(key: "date", ascending: true)
  fetchRequest.sortDescriptors = [sort1, sort2]
  . . .
  let fetchedResultsController = NSFetchedResultsController(
    fetchRequest: fetchRequest,
    managedObjectContext: self.managedObjectContext,
    sectionNameKeyPath: "category",              // change this
    cacheName: "地点")
override func numberOfSections(in tableView: UITableView) 
              -> Int {
  return fetchedResultsController.sections!.count
}

override func tableView(_ tableView: UITableView, 
    titleForHeaderInSection section: Int) -> String? {
  let sectionInfo = fetchedResultsController.sections![section]
  return sectionInfo.name
}
现在将在部分中分组
Tlu Juzaziotm Ako Maf Zwaokeg ew mudtaacd

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。