版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.08.30 |
前言
TextKit
框架是对Core Text
的封装,用简洁的调用方式实现了大部分Core Text
的功能。 TextKit是一个偏上层的开发框架,在iOS7
以上可用,使用它可以方便灵活处理复杂的文本布局,满足开发中对文本布局的各种复杂需求。TextKit实际上是基于CoreText的一个上层框架,其是面向对象的。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. TextKit框架详细解析 (一) —— 基本概览和应用场景(一)
2. TextKit框架详细解析 (二) —— 基本概览和应用场景(二)
3. TextKit框架详细解析 (三) —— 一个简单布局示例(一)
A UITextView with a Custom Text Kit Stack - 带有自定义文本工具包堆栈的UITextView
从sb编辑器实例化UITextView
会自动创建NSTextStorage
,NSLayoutManager
和NSTextContainer
(即Text Kit堆栈)的实例,并将所有三个实例公开为只读属性。
没有办法从sb编辑器中更改这些,但幸运的是,如果您以编程方式创建UITextView
和Text Kit
堆栈,则可以。
让我们试一试。 打开Main.storyboard
,通过展开Detail Scene / Detail / View
并选择Text View来找到NoteEditorViewController
视图。 删除此UITextView
实例。
接下来,打开NoteEditorViewController.swift
并从类中删除UITextView
outlet ,并将其替换为以下属性声明:
var textView: UITextView!
var textStorage: SyntaxHighlightTextStorage!
这两个属性适用于文本视图和自定义存储子类。
接下来从viewDidLoad
中删除以下行:
textView.text = note.contents
textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
由于您不再使用文本视图的插座,而是手动创建一个插座,因此您不再需要这些线路。
仍在NoteEditorViewController.swift
中工作,将以下方法添加到类中:
func createTextView() {
// 1. Create the text storage that backs the editor
let attrs = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
let attrString = NSAttributedString(string: note.contents, attributes: attrs)
textStorage = SyntaxHighlightTextStorage()
textStorage.appendAttributedString(attrString)
let newTextViewRect = view.bounds
// 2. Create the layout manager
let layoutManager = NSLayoutManager()
// 3. Create a text container
let containerSize = CGSize(width: newTextViewRect.width, height: CGFloat.max)
let container = NSTextContainer(size: containerSize)
container.widthTracksTextView = true
layoutManager.addTextContainer(container)
textStorage.addLayoutManager(layoutManager)
// 4. Create a UITextView
textView = UITextView(frame: newTextViewRect, textContainer: container)
textView.delegate = self
view.addSubview(textView)
}
这是很多代码。 让我们依次考虑每个步骤:
- 1)实例化自定义文本存储的实例,并使用包含note内容的属性字符串对其进行初始化。
- 2)创建布局管理器。
- 3)创建一个文本容器并将其与布局管理器关联。 然后,将布局管理器与文本存储相关联。
- 4)使用自定义文本容器创建实际文本视图,设置代理,并将文本视图添加为子视图。
此时,前面的图表以及它在四个关键类(storage, layout manager, container and text view)
之间显示的关系应该更有意义:
请注意,文本容器的宽度与视图宽度相匹配,但具有无限高度 - 或者尽可能接近CGFLOAT_MAX
无穷大。 无论如何,这足以让UITextView
滚动并容纳长文本段落。
现在仍然在NoteEditorViewController.swift
中工作,直接在viewDidLoad
中的super.viewDidLoad()
行之后添加以下行:
createTextView()
最后一点:在代码中创建的自定义视图不会自动继承故事板中设置的布局约束;因此,当设备方向更改时,新视图的frame不会调整大小。 您需要自己明确设置frame。
为此,请将以下行添加到viewDidLayoutSubviews
的末尾:
textView.frame = view.bounds
构建并运行您的应用程序。打开note并编辑文本,同时密切关注Xcode控制台。 您应该在键入时看到一连串的日志消息,如下所示:
这只是SyntaxHighlightTextStorage
中的日志代码,可以指示您实际调用了自定义文本处理代码。
你的文本解析器的基础似乎相当坚实 - 现在添加动态格式!
Dynamic Formatting - 动态格式
在下一步中,您将修改自定义文本存储以包围由星号包围的文本。
打开SyntaxHighlightTextStorage.swift
并添加以下方法:
func applyStylesToRange(searchRange: NSRange) {
// 1. create some fonts
let fontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleBody)
let boldFontDescriptor = fontDescriptor.fontDescriptorWithSymbolicTraits(.TraitBold)
let boldFont = UIFont(descriptor: boldFontDescriptor, size: 0)
let normalFont = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
// 2. match items surrounded by asterisks
let regexStr = "(\\*\\w+(\\s\\w+)*\\*)"
let regex = NSRegularExpression(pattern: regexStr, options: nil, error: nil)!
let boldAttributes = [NSFontAttributeName : boldFont]
let normalAttributes = [NSFontAttributeName : normalFont]
// 3. iterate over each match, making the text bold
regex.enumerateMatchesInString(backingStore.string, options: nil, range: searchRange) {
match, flags, stop in
let matchRange = match.rangeAtIndex(1)
self.addAttributes(boldAttributes, range: matchRange)
// 4. reset the style to the original
let maxRange = matchRange.location + matchRange.length
if maxRange + 1 < self.length {
self.addAttributes(normalAttributes, range: NSMakeRange(maxRange, 1))
}
}
}
上面的代码执行以下操作:
- 1)创建粗体和普通字体,以使用字体描述符
(font descriptors)
格式化文本。字体描述符可帮助您避免使用硬编码字体字符串来设置字体类型和样式。 - 2)创建一个正则表达式(或正则表达式),用于查找由星号包围的任何文本;例如,在字符串
“iOS 8 is *awesome* isn't it?”
中,存储在上面的regexStr
中的正则表达式将匹配并返回文本“* awesome *”
。如果你不完全熟悉正则表达式,请不要担心;稍后我们会详细介绍它们。 - 3)枚举正则表达式返回的匹配项,并将粗体属性应用于每个匹配项。
- 4)将匹配字符串中最后一个星号后面的字符的文本样式重置为“normal”。这可确保在关闭星号后添加的任何文本都不以粗体显示。
注意:字体描述符是一种描述符语言,允许您通过应用特定属性来修改字体,或获取字体度量的详细信息,而无需实例化UIFont实例。
在上面的代码后面添加以下方法:
func performReplacementsForRange(changedRange: NSRange) {
var extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRangeForRange(NSMakeRange(changedRange.location, 0)))
extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRangeForRange(NSMakeRange(NSMaxRange(changedRange), 0)))
applyStylesToRange(extendedRange)
}
上面的代码扩展了代码在尝试匹配粗体格式模式时检查的范围。 这是必需的,因为changedRange
通常表示单个字符;lineRangeForRange
将该范围扩展到整个文本行。
最后,在上面的代码之后添加以下方法:
override func processEditing() {
performReplacementsForRange(self.editedRange)
super.processEditing()
}
processEditing
发送文本更改为布局管理器的通知。
构建并运行您的应用程序。在note中键入一些文本,并用星号包围一些文本。 文本将自动加粗,如下面的屏幕截图所示:
这非常方便 - 您可能会考虑可能添加到文本中的所有其他样式。
Adding Further Styles - 添加更多样式
将样式应用于分隔文本的基本原则非常简单:使用正则表达式查找和替换分隔字符串,使用applyStylesToRange设置所需的文本样式。
打开SyntaxHighlightTextStorage.swift
并将以下方法添加到类中:
func createAttributesForFontStyle(style: String, withTrait trait: UIFontDescriptorSymbolicTraits) -> [NSObject : AnyObject] {
let fontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleBody)
let descriptorWithTrait = fontDescriptor.fontDescriptorWithSymbolicTraits(trait)
let font = UIFont(descriptor: descriptorWithTrait, size: 0)
return [NSFontAttributeName : font]
}
此方法将提供的字体样式应用于正文字体。 它为UIFont(descriptor:size :)
构造函数提供零大小,强制UIFont
返回与用户当前字体大小首选项匹配的大小。
接下来,将以下属性和函数添加到类中:
var replacements: [String : [NSObject : AnyObject]]!
func createHighlightPatterns() {
let scriptFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptorFamilyAttribute : "Zapfino"])
// 1. base our script font on the preferred body font size
let bodyFontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleBody)
let bodyFontSize = bodyFontDescriptor.fontAttributes()[UIFontDescriptorSizeAttribute] as NSNumber
let scriptFont = UIFont(descriptor: scriptFontDescriptor, size: CGFloat(bodyFontSize.floatValue))
// 2. create the attributes
let boldAttributes = createAttributesForFontStyle(UIFontTextStyleBody, withTrait:.TraitBold)
let italicAttributes = createAttributesForFontStyle(UIFontTextStyleBody, withTrait:.TraitItalic)
let strikeThroughAttributes = [NSStrikethroughStyleAttributeName : 1]
let scriptAttributes = [NSFontAttributeName : scriptFont]
let redTextAttributes = [NSForegroundColorAttributeName : UIColor.redColor()]
// construct a dictionary of replacements based on regexes
replacements = [
"(\\*\\w+(\\s\\w+)*\\*)" : boldAttributes,
"(_\\w+(\\s\\w+)*_)" : italicAttributes,
"([0-9]+\\.)\\s" : boldAttributes,
"(-\\w+(\\s\\w+)*-)" : strikeThroughAttributes,
"(~\\w+(\\s\\w+)*~)" : scriptAttributes,
"\\s([A-Z]{2,})\\s" : redTextAttributes
]
}
以下是此方法的内容:
- 首先,使用
Zapfino
作为字体创建“脚本”样式。 字体描述符有助于确定当前首选的正文字体大小,从而确保脚本字体也符合用户首选的文本大小设置。 - 接下来,构造要应用于每个匹配样式模式的属性。 您将在稍后看到
createAttributesForFontStyle(withTrait :)
;现在把它停下来。 - 最后,创建一个将正则表达式映射到上面声明的属性的字典。
如果你对正则表达式不是很熟悉,那么上面的字典可能看起来有点奇怪。 但是如果你逐个解构它包含的正则表达式,你可以不费力地解码它们。
采用上面实现的第一个正则表达式,它匹配星号包围的单词:
(\\*\\w+(\\s\\w+)*\\*)
双斜杠是必须使用额外的反斜杠转义文字字符串中的特殊字符的结果。 如果你抛出转义的反斜杠,并只考虑核心正则表达式,它看起来像这样:
(\*\w+(\s\w+)*\*)
现在,逐步解构正则表达式:
- 1)
(\ *
- 匹配星号 - 2)
\ w +
- 后跟一个或多个“单词”字符 - 3)
(\ s \ w +)*
- 后跟零个或多个空格组,后跟“单词”字符 - 4)
\ *)
- 后面跟一个星号
作为练习,使用上面的解释和备忘单作为指导,自己解码其他正则表达式。 你可以自己做多少?
这是给你的问题。 你能用简单的英文描述正则表达式:
\ s([A-Z] {2,})\ s
匹配?
由空格包围的两个或多个大写字母组成的任何字符串。
您还需要初始化替换字典。 将以下类初始值设定项添加到SyntaxHighlightTextStorage
类:
override init() {
super.init()
createHighlightPatterns()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
您在项目的其余部分中调用了没有参数的普通初始值设定项。
最后,使用以下内容替换applyStylesToRange()
的实现:
func applyStylesToRange(searchRange: NSRange) {
let normalAttrs = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
// iterate over each replacement
for (pattern, attributes) in replacements {
let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil)!
regex.enumerateMatchesInString(backingStore.string, options: nil, range: searchRange) {
match, flags, stop in
// apply the style
let matchRange = match.rangeAtIndex(1)
self.addAttributes(attributes, range: matchRange)
// reset the style to the original
let maxRange = matchRange.location + matchRange.length
if maxRange + 1 < self.length {
self.addAttributes(normalAttrs, range: NSMakeRange(maxRange, 1))
}
}
}
}
以前,此方法只执行一次正则表达式搜索粗体文本。 现在它做了同样的事情,但它遍历正则表达式匹配和属性的字典,因为有许多文本样式要查找。 对于每个正则表达式,它运行搜索并将指定的样式应用于匹配的模式。
请注意,NSRegularExpression
的初始化可能会失败,因此在此隐式解包。 如果由于某种原因,模式中存在错误导致模式编译失败,则代码将在此行上失败,从而迫使您修复模式,而不是进一步失败。
构建并运行您的应用程序,并运用您可以使用的所有新样式,如下所示:
这是一个更具挑战性的练习。 如果您在注释中输入文本:"*This is not bold*"
(不带引号),您会发现它不会变为粗体。 换句话说,如果所选文本在单词之间有多个空格,则不匹配。
你能创建一个会鼓励那个文本的正则表达式吗? 它只是对代码中已经存在的修改的简单修改。
(\*\w+(\s+\w+)*\*) – try it out!
你的应用程序已接近完成;只有一些小问题要处理。
如果您在处理应用程序时更改了屏幕方向,则您已经注意到应用程序不再响应内容大小更改通知,因为您的自定义实现尚不支持此操作。
至于第二个问题,如果你在note中添加了大量文字,你会发现文本视图的底部被键盘部分遮挡了;当你看不到你正在打字的东西时,输入东西有点困难!
是时候解决这两个问题了。
Reviving Dynamic Type - 恢复动态类型
要更正动态类型的问题,您的代码应在内容大小更改通知发生时更新包含note文本的属性字符串使用的字体。
将以下函数添加到SyntaxHighlightTextStorage
类:
func update() {
// update the highlight patterns
createHighlightPatterns()
// change the 'global' font
let bodyFont = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
addAttributes(bodyFont, range: NSMakeRange(0, length))
// re-apply the regex matches
applyStylesToRange(NSMakeRange(0, length))
}
上述方法更新与各种正则表达式关联的所有字体,将正文文本样式应用于整个字符串,然后重新应用高亮样式。
最后,打开NoteEditorViewController.swift
并修改preferredContentSizeChanged()
以执行更新:
func preferredContentSizeChanged(notification: NSNotification) {
textStorage.update()
updateTimeIndicatorFrame()
}
构建并运行,更改文本大小首选项,文本应相应调整,如下例所示:
Resizing Text Views - 调整文本视图的大小
剩下要做的就是解决编辑长notes时键盘遮住文本视图下半部分的问题。 这是iOS 8尚未为我们解决的一个问题!
要解决此问题,您可以在键盘可见时缩小文本视图框的大小。
在调用createTextView()
之后立即打开NoteEditorViewController.swift
并将以下行添加到viewDidLoad()
:
textView.scrollEnabled = true
这样可以在注释编辑器视图中滚动文本视图。
现在将以下代码添加到viewDidLoad()
的底部:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidHide:", name: UIKeyboardDidHideNotification, object: nil)
这会添加键盘显示或隐藏时的通知,这是相应调整文本视图框大小的信号。
接下来,将以下方法添加到类中:
func updateTextViewSizeForKeyboardHeight(keyboardHeight: CGFloat) {
textView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height - keyboardHeight)
}
此方法可降低文本视图的高度以适应键盘。
最后,您需要实现两种方法来响应通知。 将以下方法添加到类中:
func keyboardDidShow(notification: NSNotification) {
if let rectValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue {
let keyboardSize = rectValue.CGRectValue().size
updateTextViewSizeForKeyboardHeight(keyboardSize.height)
}
}
func keyboardDidHide(notification: NSNotification) {
updateTextViewSizeForKeyboardHeight(0)
}
当显示键盘时,您需要从通知中提取键盘大小,从那里获得键盘高度很简单。 当键盘隐藏时,您只需将高度调整重置为零。
注意:虽然在早期版本的iOS上,您需要在计算新文本视图大小时考虑当前屏幕方向(因为当屏幕方向更改时,UIView实例的宽度和高度属性会被交换,但键盘的宽度和高度属性不会),iOS 8不再需要这个。
构建并运行您的应用程序,编辑note并检查显示键盘不再遮挡文本,如下所示:
注意:在撰写本文时,iOS 8存在一个微妙的错误 - 当文本视图调整大小时,光标位置可能仍然在屏幕外。 如果用户点击
“return”
键,光标将移动到正确的位置。 我们将密切关注这一点,如果错误持续存在,我们将尝试寻找替代解决方案。
源码
1. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// style the navigation bar
let navColor = UIColor(red: 0.175, green: 0.458, blue: 0.831, alpha: 1.0)
UINavigationBar.appearance().barTintColor = navColor
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName : UIColor.whiteColor()]
// make the status bar white
application.statusBarStyle = .LightContent
return true
}
}
2. Note.swift
import Foundation
class Note {
var contents: String
let timestamp: NSDate
// an automatically generated note title, based on the first line of the note
var title: String {
// split into lines
let lines = contents.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
// return the first
return lines[0]
}
init(text: String) {
contents = text
timestamp = NSDate()
}
}
// some default notes to play with
var notes = [
Note(text: "Shopping List\r\r1. Cheese\r2. Biscuits\r3. Sausages\r4. IMPORTANT Cash for going out!\r5. -potatoes-\r6. A copy of iOS8 by tutorials\r7. A new iPhone\r8. A present for mum"),
Note(text: "Meeting notes\rA long and drawn out meeting, it lasted hours and hours and hours!"),
Note(text: "Perfection ... \n\nPerfection is achieved not when there is nothing left to add, but when there is nothing left to take away - Antoine de Saint-Exupery"),
Note(text: "Notes on Swift\nThis new language from Apple is changing iOS development as we know it!"),
Note(text: "Meeting notes\rA dfferent meeting, just as long and boring"),
Note(text: "A collection of thoughts\rWhy do birds sing? Why is the sky blue? Why is it so hard to create good test data?")
]
3. NoteEditorViewController.swift
import UIKit
class NoteEditorViewController: UIViewController, UITextViewDelegate {
var textView: UITextView!
var textStorage: SyntaxHighlightTextStorage!
var note: Note!
var timeView: TimeIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
createTextView()
textView.scrollEnabled = true
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "preferredContentSizeChanged:",
name: UIContentSizeCategoryDidChangeNotification,
object: nil)
timeView = TimeIndicatorView(date: note.timestamp)
textView.addSubview(timeView)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidHide:", name: UIKeyboardDidHideNotification, object: nil)
}
func preferredContentSizeChanged(notification: NSNotification) {
textStorage.update()
updateTimeIndicatorFrame()
}
func textViewDidEndEditing(textView: UITextView!) {
note.contents = textView.text
}
override func viewDidLayoutSubviews() {
updateTimeIndicatorFrame()
textView.frame = view.bounds
}
func updateTimeIndicatorFrame() {
timeView.updateSize()
timeView.frame = CGRectOffset(timeView.frame, textView.frame.width - timeView.frame.width, 0)
let exclusionPath = timeView.curvePathWithOrigin(timeView.center)
textView.textContainer.exclusionPaths = [exclusionPath]
}
func createTextView() {
// 1. Create the text storage that backs the editor
let attrs = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
let attrString = NSAttributedString(string: note.contents, attributes: attrs)
textStorage = SyntaxHighlightTextStorage()
textStorage.appendAttributedString(attrString)
let newTextViewRect = view.bounds
// 2. Create the layout manager
let layoutManager = NSLayoutManager()
// 3. Create a text container
let containerSize = CGSize(width: newTextViewRect.width, height: CGFloat.max)
let container = NSTextContainer(size: containerSize)
container.widthTracksTextView = true
layoutManager.addTextContainer(container)
textStorage.addLayoutManager(layoutManager)
// 4. Create a UITextView
textView = UITextView(frame: newTextViewRect, textContainer: container)
textView.delegate = self
view.addSubview(textView)
}
func updateTextViewSizeForKeyboardHeight(keyboardHeight: CGFloat) {
textView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height - keyboardHeight)
}
func keyboardDidShow(notification: NSNotification) {
if let rectValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue {
let keyboardSize = rectValue.CGRectValue().size
updateTextViewSizeForKeyboardHeight(keyboardSize.height)
}
}
func keyboardDidHide(notification: NSNotification) {
updateTextViewSizeForKeyboardHeight(0)
}
}
4. NotesListViewController.swift
import UIKit
class NotesListViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "preferredContentSizeChanged:",
name: UIContentSizeCategoryDidChangeNotification,
object: nil)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// whenever this view controller appears, reload the table. This allows it to reflect any changes
// made whilst editing notes
tableView.reloadData()
}
func preferredContentSizeChanged(notification: NSNotification) {
tableView.reloadData()
}
// #pragma mark - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let note = notes[indexPath.row]
let font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
let textColor = UIColor(red: 0.175, green: 0.458, blue: 0.831, alpha: 1)
let attributes = [
NSForegroundColorAttributeName : textColor,
NSFontAttributeName : font,
NSTextEffectAttributeName : NSTextEffectLetterpressStyle
]
let attributedString = NSAttributedString(string: note.title, attributes: attributes)
cell.textLabel?.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.textLabel?.attributedText = attributedString
return cell
}
let label: UILabel = {
let temporaryLabel = UILabel(frame: CGRect(x: 0, y: 0, width: Int.max, height: Int.max))
temporaryLabel.text = "test"
return temporaryLabel
}()
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
label.sizeToFit()
return label.frame.height * 1.7
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
notes.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
// #pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if let editorVC = segue.destinationViewController as? NoteEditorViewController {
if "CellSelected" == segue.identifier {
if let path = tableView.indexPathForSelectedRow() {
editorVC.note = notes[path.row]
}
} else if "AddNewNote" == segue.identifier {
let note = Note(text: " ")
editorVC.note = note
notes.append(note)
}
}
}
}
5. TimeIndicatorView.swift
import UIKit
class TimeIndicatorView: UIView {
var label = UILabel()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(date: NSDate) {
super.init(frame: CGRectZero)
// Initialization code
backgroundColor = UIColor.clearColor()
clipsToBounds = false
// format and style the date
let formatter = NSDateFormatter()
formatter.dateFormat = "dd\rMMMM\ryyyy"
let formattedDate = formatter.stringFromDate(date)
label.text = formattedDate.uppercaseString
label.textAlignment = .Center
label.textColor = UIColor.whiteColor()
label.numberOfLines = 0
addSubview(label)
}
func updateSize() {
// size the label based on the font
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
label.frame = CGRect(x: 0, y: 0, width: Int.max, height: Int.max)
label.sizeToFit()
// set the frame to be large enough to accomodate the circle that surrounds the text
let radius = radiusToSurroundFrame(label.frame)
frame = CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2)
// center the label within this circle
label.center = center
// offset the center of this view to ... erm ... can I just draw you a picture?
// You know the story - the designer provides a mock-up with some static data, leaving
// you to work out the complex calculations required to accomodate the variability of real-world
// data. C'est la vie!
let padding : CGFloat = 5.0
center = CGPoint(x: center.x + label.frame.origin.x - padding, y: center.y - label.frame.origin.y + padding)
}
// calculates the radius of the circle that surrounds the label
func radiusToSurroundFrame(frame: CGRect) -> CGFloat {
return max(frame.width, frame.height) * 0.5 + 25
}
func curvePathWithOrigin(origin: CGPoint) -> UIBezierPath {
return UIBezierPath(arcCenter: origin, radius: radiusToSurroundFrame(label.frame), startAngle: -180, endAngle: 180, clockwise: true)
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetShouldAntialias(context, true)
let path = curvePathWithOrigin(label.center)
UIColor(red: 0.329, green: 0.584, blue: 0.898, alpha: 1).setFill()
path.fill()
}
}
6. SyntaxHighlightTextStorage.swift
import UIKit
class SyntaxHighlightTextStorage: NSTextStorage {
let backingStore = NSMutableAttributedString()
var replacements: [String : [NSObject : AnyObject]]!
override init() {
super.init()
createHighlightPatterns()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var string: String {
return backingStore.string
}
override func attributesAtIndex(index: Int, effectiveRange range: NSRangePointer) -> [NSObject : AnyObject] {
return backingStore.attributesAtIndex(index, effectiveRange: range)
}
override func replaceCharactersInRange(range: NSRange, withString str: String) {
println("replaceCharactersInRange:\(range) withString:\(str)")
beginEditing()
backingStore.replaceCharactersInRange(range, withString:str)
edited(.EditedCharacters | .EditedAttributes, range: range, changeInLength: (str as NSString).length - range.length)
endEditing()
}
override func setAttributes(attrs: [NSObject : AnyObject]!, range: NSRange) {
println("setAttributes:\(attrs) range:\(range)")
beginEditing()
backingStore.setAttributes(attrs, range: range)
edited(.EditedAttributes, range: range, changeInLength: 0)
endEditing()
}
func applyStylesToRange(searchRange: NSRange) {
let normalAttrs = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
// iterate over each replacement
for (pattern, attributes) in replacements {
let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil)!
regex.enumerateMatchesInString(backingStore.string, options: nil, range: searchRange) {
match, flags, stop in
// apply the style
let matchRange = match.rangeAtIndex(1)
self.addAttributes(attributes, range: matchRange)
// reset the style to the original
let maxRange = matchRange.location + matchRange.length
if maxRange + 1 < self.length {
self.addAttributes(normalAttrs, range: NSMakeRange(maxRange, 1))
}
}
}
}
func performReplacementsForRange(changedRange: NSRange) {
var extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRangeForRange(NSMakeRange(changedRange.location, 0)))
extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRangeForRange(NSMakeRange(NSMaxRange(changedRange), 0)))
applyStylesToRange(extendedRange)
}
override func processEditing() {
performReplacementsForRange(self.editedRange)
super.processEditing()
}
func createAttributesForFontStyle(style: String, withTrait trait: UIFontDescriptorSymbolicTraits) -> [NSObject : AnyObject] {
let fontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleBody)
let descriptorWithTrait = fontDescriptor.fontDescriptorWithSymbolicTraits(trait)
let font = UIFont(descriptor: descriptorWithTrait, size: 0)
return [NSFontAttributeName : font]
}
func createHighlightPatterns() {
let scriptFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptorFamilyAttribute : "Zapfino"])
// 1. base our script font on the preferred body font size
let bodyFontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleBody)
let bodyFontSize = bodyFontDescriptor.fontAttributes()[UIFontDescriptorSizeAttribute] as NSNumber
let scriptFont = UIFont(descriptor: scriptFontDescriptor, size: CGFloat(bodyFontSize.floatValue))
// 2. create the attributes
let boldAttributes = createAttributesForFontStyle(UIFontTextStyleBody, withTrait:.TraitBold)
let italicAttributes = createAttributesForFontStyle(UIFontTextStyleBody, withTrait:.TraitItalic)
let strikeThroughAttributes = [NSStrikethroughStyleAttributeName : 1]
let scriptAttributes = [NSFontAttributeName : scriptFont]
let redTextAttributes = [NSForegroundColorAttributeName : UIColor.redColor()]
// construct a dictionary of replacements based on regexes
replacements = [
"(\\*\\w+(\\s\\w+)*\\*)" : boldAttributes,
"(_\\w+(\\s\\w+)*_)" : italicAttributes,
"([0-9]+\\.)\\s" : boldAttributes,
"(-\\w+(\\s\\w+)*-)" : strikeThroughAttributes,
"(~\\w+(\\s\\w+)*~)" : scriptAttributes,
"\\s([A-Z]{2,})\\s" : redTextAttributes
]
}
func update() {
// update the highlight patterns
createHighlightPatterns()
// change the 'global' font
let bodyFont = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
addAttributes(bodyFont, range: NSMakeRange(0, length))
// re-apply the regex matches
applyStylesToRange(NSMakeRange(0, length))
}
}
后记
本篇主要讲述了一个简单布局示例,感兴趣的给个赞或者关注~~~