[Swift]Day18&19:一个简单的例子

Swift90Days - 一个简单的小应用

第0步:明确任务

经过前面基础语法的学习,我们终于可以真枪实弹的来一发了。以这篇小鸡鸡小猫猫小狗狗为例,我们将会创建一个简单的应用:

  • 通过 UITableView 展示三种小动物
  • 通过 NSUserDefaults 进行数据持久化
  • 通过 UIWebView 从维基百科加载更多数据

由于时间有限,这篇博客并不是教程或者翻译,只是一个关键思路的整理和记录,完整的源代码在文末有链接,如果有任何疑问欢迎与我联系,谢谢。

第1步:创建项目

创建一个新的项目,模板选择 Single View Application ,项目名称叫做:BirdsCatsDogs。

第2步:简单重构

系统为我们自动生成了 ViewController.swift ,将其改为SpeciesViewController.swift ,记得也改下类的名字。然后在 StoryBoard (以后简称为 SB 希望不要误解) 中设置 custum class,如果设置正确在输入的时候是有自动补全的,回车即可。

第3步:添加导航

拖拽一个 UINavigationController 到 SB 中,设置成 Initial View Controller,然后把 SpeciesViewController设置成它的 root view controller 。把 SpeciesViewController 的 title 设置成 Species 。

运行一下,确保没有问题。(不可能有问题,这时候运行一般是满足自己内心的成就感。)

第4步:加载数据

在这里我们用 NSUserDefaults 加载数据,通常它用来存储一些系统配置,比如字体大小啊之类的。

我们新建一个 DataManager.swift ,通过单例模式实现一个数据管理器:

import Foundation

class DataManager {
    struct Static {
        static var onceToken : dispatch_once_t = 0
        static var instance : DataManager? = nil
    }

    class var sharedInstance : DataManager {
        dispatch_once(&Static.onceToken) {
            Static.instance = DataManager()
        }
        return Static.instance!
    }
}

这段代码是原文的代码,有些地方可以参考:

  • 静态变量通过内嵌 Static 结构体存储。
  • 单例模式通过 dispatch_once 实现,通过 sharedInstance 获取。 (GCD的内容后面再补充)

接下来我们在 DataManager 里面添加一个变量:species,类型为 [String:[String]]。在 init() 里加上一些初始化的工作:

var species: [String:[String]]

init() {
    let userDefaults = NSUserDefaults.standardUserDefaults()
    if let speciesInfo = userDefaults.valueForKey("species") as? [String:[String]] {
        species = speciesInfo
    } else {
        species = [
            "Birds": ["Swift"],
            "Cats" : ["Persian Cat"],
            "Dogs" : ["Labrador Retriever"]
        ]
    }
}

我们可以通过 DataManager.sharedInstance.species 获取各个种类的数据。

Tips:类似于单例模式这种可能会多次用到的代码片段,建议加到 Xcode 的 Snippets 里。

第5步:加载列表

我们用字典存储了数据,通过 key 值获取数据十分方便。但是字典本身是无序的,而像 UITableView 这种列表的数据本身是有序的。所以添加一个计算属性 speciesList ,可以获取排序之后的列表并返回:

var speciesList: [String] {
    var list: [String] = []
    for speciesName in species.keys {
        list.append(speciesName)
    }
    list.sort(<)
    return list
}

回到 SpeciesViewController 里,我们可以这样获取数据:

var species: [String] = DataManager.sharedInstance.speciesList

第6步:列表视图

拖拖拽拽设置好 UITableView ,具体过程就不赘述了,可以直接打开项目看看。tableview 相关的部分代码如下:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
    cell.textLabel?.text = species[indexPath.row]
    return cell
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return species.count
}

运行一下,确保没有问题。(这时候就不是成就感的问题了,测试 SB 和代码的连接情况。)

第7步:详情页面

我们再创建一个 RacesViewController ,用来展示当前种类下的数据列表:

class RacesViewController: UIViewController {
    var species: String!

    override func viewDidLoad() {
        super.viewDidLoad()

        title = species
    }
}

注意在 StoryBoard 里设置这个 RacesViewController 的 StoryBoard ID ,这样我们在做点击事件的时候可以获取到这个 RacesViewController 然后进行 pushViewController 操作。

第8步:选中事件

回到 SpeciesViewController 里,添加单元格的选中事件:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    var racesViewController = storyboard?.instantiateViewControllerWithIdentifier("RacesViewController") as RacesViewController
    racesViewController.species = species[indexPath.row]
    navigationController?.pushViewController(racesViewController, animated: true)
}

instantiateViewControllerWithIdentifier 可以通过 StoryBoard ID 初始化 ViewController 。

第9步:展示种类

这个步骤和第6步基本相同, tableview 相关的代码:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return races.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("RaceCell") as UITableViewCell
    cell.textLabel?.text = races[indexPath.row]
    cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
    return cell
}

这时再测试一下,点击 cell 之后会跳转到另一个表格列表里。

第10步:保存修改

给 DataManager 加个添加的方法:

func saveData() {
    let userDefaults = NSUserDefaults.standardUserDefaults()
    userDefaults.setValue(species, forKey: "species")
}

func addRace(species inSpecies: String, race: String) {
    if var races = species[inSpecies] {
        races.append(race)
        species[inSpecies] = races
    }

    saveData()
}

saveData 方法用来写入本地,addRace 方法供外部调用,添加一条记录。

第11步:添加按钮

给导航栏加个 Add 按钮,并且关联 didTapAdd 这个 IBAction 。

第12步:弹出视图

使用 UIAlerView 弹出视图输入内容,注意设置 style 为 PlainTextInput ,设置 delegate 为 self 。

@IBAction func didTapAdd() {
    var alert = UIAlertView(title: "New Race", message: "Type in a new race", delegate: self,
        cancelButtonTitle: "Cancel", otherButtonTitles: "Add")
    alert.alertViewStyle = UIAlertViewStyle.PlainTextInput
    alert.show()
}

然后实现 alertView 的委托方法:

func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) {
    if buttonIndex == 1 {
        var textField = alertView.textFieldAtIndex(0)!
        var newRace = textField.text
        DataManager.sharedInstance.addRace(species: species, race: newRace)
        var newIndexPath = NSIndexPath(forRow: races.count - 1, inSection: 0)
        myTableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
    }
}

这个时候再运行一下测试一下添加功能是不是OK。

第13步:删除数据

和添加数据一样,我们先去 DataManager 加个删除数据的方法:

func removeRace(species inSpecies: String, race inRace: String) {
    if var races = species[inSpecies] {
        var index = -1

        for (idx, race) in enumerate(races) {
            if race == inRace {
                index = idx
                break
            }
        }

        if index != -1 {
            races.removeAtIndex(index)
            species[inSpecies] = races
            saveData()
        }

    }
}

有几个值得注意的地方:

  • 通过 index 设置为 -1 作为标识,防止出现搜索到最后也没找到的结局。
  • 通过 enumerate 来遍历数组,既不用 ++i 式的遍历,又可以获取索引值。

然后回到 RacesViewController ,添加删除相关的委托方法:

func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    return true
}

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    var raceToRemove = races[indexPath.row]
    DataManager.sharedInstance.removeRace(species: species, race: raceToRemove)
    tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}

试一下删除操作,OK没问题。

完结

后面 WebView 的内容没什么亮点,大同小异,不再记录。

匆匆记录了一些关键点,如果想完整学习请参考引用中的教程。

其实整个项目的功能很简单,不过凑头到尾走一遍可以体验一下其他程序员的开发思路和基本步骤。总之还是有些收获的。不过边看边记录效率太低,以后只会记录关键的收获,不再啰嗦重复的内容。

完整代码地址:BirdsCatsDogs


References

时间: 2024-08-11 07:48:35

[Swift]Day18&19:一个简单的例子的相关文章

Android Handler的一个简单使用例子

在前面 开启一个线程Thread并用进度条显示进度 这篇文章里,我们用线程实现了这么一个简单的功能,就是点击按钮,加载进度条.但是有没有发现,点击一次之后,再次点击就会没效.我们可是需要每次点击都要显示下一张图片的.永盈会娱乐城 这里就需要引入 Android 的消息机制了,简单来说,就是 Handler.Looper 还有 Message Queue的使用.这里我们用一个简单的例子来说明 Handler 的使用,就是每次点击按钮,给消息队列发送一个数字 5.还是在 PaintingActivi

【Python】一个简单的例子

问题描述: Python基础篇 参考资料: (1)http://www.cnblogs.com/octobershiner/archive/2012/12/04/2801670.html (2)http://www.cnblogs.com/itech/archive/2010/06/20/1760345.html 例子: 求解Fibonacci glb_var.py gl_count=1 path.py # coding:utf-8 ''' Created on 2014-4-28 @autho

关于apriori算法的一个简单的例子

apriori算法是关联规则挖掘中很基础也很经典的一个算法,我认为很多教程出现大堆的公式不是很适合一个初学者理解.因此,本文列举一个简单的例子来演示下apriori算法的整个步骤. 下面这个表格是代表一个事务数据库D,其中最小支持度为50%,最小置信度为70%,求事务数据库中的频繁关联规则. Tid 项目集 1  面包,牛奶,啤酒,尿布 2  面包,牛奶,啤酒 3  啤酒,尿布 4  面包,牛奶,花生 apriori算法的步骤如下所示: (1)生成候选频繁1-项目集C1={{面包},{牛奶},{

一个简单的例子让你了解React-Redux

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 一个简单的例子让你了解React-Redux - 小平果的欢乐谷 - 博客频道 - CSDN.NET 小平果的欢乐谷 你的到来会让我很意外,谢谢光临! 目录视图 摘要视图 订阅 [活动]2017 CSDN博客专栏评选 &nbsp [5月书讯]流畅的Python,终于等

Linux内核中的信号机制--一个简单的例子【转】

本文转载自:http://blog.csdn.net/ce123_zhouwei/article/details/8562958 Linux内核中的信号机制--一个简单的例子 Author:ce123(http://blog.csdn.NET/ce123) 信号机制是类UNIX系统中的一种重要的进程间通信手段之一.我们经常使用信号来向一个进程发送一个简短的消息.例如:假设我们启动一个进程通过socket读取远程主机发送过来的网络数据包,此时由于网络因素当前主机还没有收到相应的数据,当前进程被设置

Swift语言编写一个简单的条形码扫描APP

swift语言编写一个简单的条形码扫描APP 原文地址:appcoda 在处理职员在杂货店的收银台排了很长的队伍,在机场帮助检查背包和旅客,或者在主要的食品供应商,协助处理乏味的存货清单过程,条形码扫描是很简单的处理工具.实际上,他们已经用了这个办法来解决消费者在智能购物,图书分类,等其他目的.因此,让我们来制作一个iPhone版本的条形码扫描工具吧! 对我们来说幸运的是,苹果已经制作了条形码扫描的程序,实现它是一件很简单的事情.我们将要研究进入AV Foundation框架的世界,组建APP,

duilib DirectUI库里面的一个简单的例子RichListDemo

http://blog.csdn.net/zengraoli/article/details/9993153 2013-08-16 00:08 3289人阅读 评论(2) 收藏 举报 目录(?)[+] 1.首先来看这里的CRichListWnd 已经不再是从CWindowWnd继承了 classCRichListWnd:publicWindowImplBase 从WindowImplBase中,可以看到有三个抽象函数: virtualCDuiStringGetSkinFolder()=0; vi

一个简单的例子搞懂ES6之Promise

ES5中实现异步的常见方式不外乎以下几种: 1. 回调函数 2. 事件驱动 2. 自定义事件(根本上原理同事件驱动相同) 而ES6中的Promise的出现就使得异步变得非常简单.promise中的异步是这样的: * 每当我需要执行一次异步操作的时候,我都需要new一个promise对象 * 每一个异步操作的Promise对象都需要设定一个成功执行的条件和成功的回调.一个失败的条件和失败的回调 * Promise对象可通过执行then()方法获得成功的回调信息 * Promise对象可通过执行ca

020: class, objects and instance: 一个简单的例子,压缩文件中内容的替换

这个例子是对前面学习的知道的一个简单总结. 在设计类的时候,并非所有的类都是埋头干活的类,同时也需要有很多类似于管理的类,这样的类的功能就是调用其他的类来共同的完成任务. import sys import os import shutil import zipfile class ZipReplace(object): def __init__(self, file_name, search_string, replace_string): self.file_name = file_name