HOME/Articles/

bian_ji_ri_ji

Article Outline

Product Code: 产品的实现 - 编辑日记

在实现了基本的创建浏览之后,对日记的编辑就是最后一件事情的了。三个红色的点和直书结合后的展示效果我非常喜欢。

添加按钮

为了实现存、改、删这3个功能,我们需要修改DiaryViewController,有3个红色的按钮需要添加,这3个按钮统一放在buttonsView里面,方便控制。

添加属性

import UIKit

class DiaryViewController: UIViewController {
    var diary:Diary!

    var webview: UIWebView!

    var saveButton:UIButton!

    var deleteButton:UIButton!

    var editButton:UIButton!

    var buttonsView:UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }

    func setupUI() {
      ...
      // 添加存改删按钮

      let buttonFontSize:CGFloat = 18.0

      saveButton = diaryButtonWith(text: "存",  fontSize: buttonFontSize,  width: 50.0,  
                                        normalImageName: "Oval", 
                                        highlightedImageName: "Oval_pressed")

      saveButton.center = CGPoint(x: buttonsView.frame.width/2.0, 
                                       y: buttonsView.frame.height/2.0)

      saveButton.addTarget(self, action: #selector(saveToRoll), 
                               for: UIControlEvents.touchUpInside)

      buttonsView.addSubview(saveButton)


      editButton = diaryButtonWith(text: "改",  fontSize: buttonFontSize,  width: 50.0, 
                                        normalImageName: "Oval", 
                                        highlightedImageName: "Oval_pressed")

      editButton.center = CGPoint(x: saveButton.center.x - 56.0, y: saveButton.center.y)

      editButton.addTarget(self, action: #selector(editDiary), 
                               for: UIControlEvents.touchUpInside)

      buttonsView.addSubview(editButton)

      deleteButton = diaryButtonWith(text: "刪",  fontSize: buttonFontSize,  width: 50.0, 
                                          normalImageName: "Oval", 
                                          highlightedImageName: "Oval_pressed")

      deleteButton.center = CGPoint(x: saveButton.center.x + 56.0, y: saveButton.center.y)

      deleteButton.addTarget(self, action: #selector(deleteThisDiary), 
                                 for: UIControlEvents.touchUpInside)

      buttonsView.addSubview(deleteButton)

      self.view.addSubview(buttonsView)
    }
}

接下来,我们需要增加一个showButtons的函数来控制这几个按钮的显示和隐藏。

func showButtons() {

    if(buttonsView.alpha == 0.0) {
        UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions(), 
            animations:
            {
                self.buttonsView.center = CGPoint(x: self.buttonsView.center.x, 
                           y: screenSize.height - self.buttonsView.frame.size.height/2.0)
                self.buttonsView.alpha = 1.0

            }, completion: nil)

    }else{

        UIView.animate(withDuration: 0.1, delay: 0, options: UIViewAnimationOptions(), 
            animations:
            {
                self.buttonsView.center = CGPoint(x: self.buttonsView.center.x, 
                           y: screenSize.height + self.buttonsView.frame.size.height/2.0)
                self.buttonsView.alpha = 0.0
            }, completion: nil)

    }
}

为了让界面更加简洁,我们设定当用户点击WebView的时候触发这个函数,在setupUI的底部加入一个 UITapGestureRecognizer:

// 切换按钮的显示状态
let mTapUpRecognizer = UITapGestureRecognizer(target: self, action: #selector(showButtons))
mTapUpRecognizer.numberOfTapsRequired = 1
self.webview.addGestureRecognizer(mTapUpRecognizer)

如果此时运行App,你会发现,虽然按钮显示出来了,但是点击 WebView 时并不会隐藏,这是因为 WebView 本身就有一些手势设定与我们新添加的这个优先级有冲突,我们需要实现 UIGestureRecognizerDelegate 中的 shouldBeRequiredToFailBy otherGestureRecognizer 协议来实现这一点,将上面的代码改为:

// 切换按钮的显示状态
let mTapUpRecognizer = UITapGestureRecognizer(target: self, action: #selector(showButtons))
mTapUpRecognizer.numberOfTapsRequired = 1
mTapUpRecognizer.delegate = self
self.webview.addGestureRecognizer(mTapUpRecognizer)
接着在DiaryViewController中增加一个扩展:
extension DiaryViewController: UIGestureRecognizerDelegate {
      // 是否应当优先识别此手势
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

现在再次运行App,点击WebView时按钮就可以在消失和显示之间切换了。

修改

修改日记只需要把日记传给我们之前的创建界面

func editDiary() {
    let composeViewController = self.storyboard?.instantiateViewController(withIdentifier: "DiaryComposeViewController") as! DiaryComposeViewController

    if let diary = diary {

        composeViewController.diary = diary
    }

    self.present(composeViewController, animated: true, completion: nil)
}

对应地我们需要修改 DiaryComposeViewController 的逻辑,当创建界面显示的时候,如果已经有 diary,那么显示即可:

if let diary = diary {
    composeView.text = diary.content

    locationTextView.text = diary.location
    if let title = diary.title {
        titleTextView.text = title
    }
}

完成的时候,针对不同的情况进行处理:

func finishCompose(_ button: UIButton) {
    // 取消输入框的编辑状态,收起键盘

    self.composeView.endEditing(true)
    self.locationTextView.endEditing(true)

    // 确保有文字内容才保存
    if (composeView.text.lengthOfBytes(using: String.Encoding.utf8) > 1){

        // 如果是修改 Diary,则保存到原日记中
        if let diary = diary {

            diary.content = composeView.text
            diary.location = locationTextView.text
            diary.title = titleTextView.text

        }else{

            let entity =  NSEntityDescription.entity(forEntityName: "Diary", in: managedContext)

            let newdiary = Diary(entity: entity!, newdiary.content = composeView.text

            if let address  = locationHelper.address {
                newdiary.location = address
            }

            if let title = titleTextView.text {
                newdiary.title = title
            }

            newdiary.updateTimeWithDate(Date())
        }

        var error: NSError?
        do {
            try managedContext.save()
        } catch let error1 as NSError {
            error = error1
            print("Could not save \(error), \(error?.userInfo)")
        }

    }

    self.dismiss(animated: true, completion: nil)
}

保存

在这里“存”这个按钮的功能是保存成图片。转换成图片保存所需要的其实就是把 WebView 全尺寸展开,然后转换成UIImage,我们在 Helper.swift 中给 UIWebView 扩展一个 captureView 的方法:

extension UIWebView {

    func captureView() -> UIImage{

        // 存储初始大小
        let tmpFrame = self.frame

        // 新的Frame

        var aFrame = self.frame

        aFrame.size.width = self.sizeThatFits(UIScreen.main.bounds.size).width

        // 展开Frame
        self.frame = aFrame

        // 初始化 ImageContext
        UIGraphicsBeginImageContextWithOptions(
            self.sizeThatFits(UIScreen.main.bounds.size),
            false,
            UIScreen.main.scale)

        // 创建新的Context

        let resizedContext = UIGraphicsGetCurrentContext()
        self.layer.render(in: resizedContext!)

        // 重新渲染到新的resizedContext
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // 还原 Frame
        self.frame = tmpFrame
        return image!
    }
}

最终在 DiaryViewController 中增加 saveToRoll 函数,利用系统的分享框架让用户选择适当的处理方式:

func saveToRoll() {

    let offset = self.webview.scrollView.contentOffset.x

    // 针对WebView截图
    let image =  webview.captureView()

    self.webview.scrollView.contentOffset.x = offset

    // 创建分享对象
    var sharingItems = [AnyObject]()

    // 给分享对象插入图片
    sharingItems.append(image)

    // 初始化分享组件
    let activityViewController = UIActivityViewController(activityItems: sharingItems, applicationActivities: nil)
    activityViewController.popoverPresentationController?.sourceView = self.saveButton

    // 现实分享组件
    self.present(activityViewController, animated: true, completion: nil)

}

删除

删除要简单得多,使用 deleteObject() 方法删除当前正在查看的 Diary,接着调用 save() 方法保存操作。最后实现 hideDiary() 返回上一层。

func deleteThisDiary() {
    managedContext.delete(diary)
    do {
        try managedContext.save()
    } catch _ {
    }
    hideDiary()
}

func hideDiary() {
    self.navigationController?.popViewController(animated: true)
}

如果此时运行App,点击删除后虽然日记已经被删除掉,但日视图依旧有这个日记,我们需要修改DiaryMonthCollectionViewController的代码,使其在日记发生变化时自动重新加载。

在viewDidLoad中修改这段代码,增加delegate委托关系:

// 查询数据

do {
    // 新建查询
    let fetchRequest = NSFetchRequest<Diary>(entityName:"Diary")

    // 增加过滤条件
    fetchRequest.predicate = NSPredicate(format:"year = \(year!) AND month = \(month!)")

    // 排序方式
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "created_at", ascending: true)]

    fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                          managedObjectContext: managedContext,
                                                          ectionNameKeyPath: nil,
                                                          cacheName: nil)

    // 建立委托关系
    fetchedResultsController.delegate = self

    // 尝试查询
    try self.fetchedResultsController.performFetch()


    if (fetchedResultsController.fetchedObjects!.count == 0){
        print("没有存储结果")
    } else {
        diarys = fetchedResultsController.fetchedObjects!
    }

} catch let error as NSError {
    NSLog("发现错误 \(error.localizedDescription)")
}

在DiaryMonthCollectionViewController的最后增加一个扩展:

extension DiaryMonthCollectionViewController: NSFetchedResultsControllerDelegate {
// 响应数据变化 
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

        // 重置数据源
        diarys = controller.fetchedObjects! as! [Diary]
        // 重载数据
        self.collectionView?.reloadData()
        // 更新布局
        self.collectionView?.setCollectionViewLayout(DiaryLayout(), animated: false)
    }
}

至此,当我们增加或者删除日记的时候,日视图就能够正确的显示了。

本节代码可从GitHub(https://github.com/ProducterTips/producter-book-examples/tree/master/Diary/Diary_10) 获得。