├── .gitignore
├── .travis.yml
├── LICENSE
├── Package.swift
├── README.md
├── Screenshots
├── InterfaceBuilder.png
└── Screenshot.png
├── TagListView.podspec
├── TagListView
├── CloseButton.swift
├── Info.plist
├── TagListView.h
├── TagListView.swift
└── TagView.swift
├── TagListViewDemo.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── TagListView.xcscheme
│ └── TagListViewDemo.xcscheme
└── TagListViewDemo
├── AppDelegate.swift
├── Base.lproj
├── LaunchScreen.xib
└── Main.storyboard
├── Images.xcassets
└── AppIcon.appiconset
│ └── Contents.json
├── Info.plist
└── ViewController.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | Pods/
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode9
3 | xcode_project: TagListViewDemo.xcodeproj
4 | xcode_scheme: TagListViewDemo
5 | xcode_sdk:
6 | - iphonesimulator11.0
7 | script:
8 | - xcodebuild -project TagListViewDemo.xcodeproj -scheme TagListViewDemo -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 LIU Dongyuan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(name: "TagListView",
7 | platforms: [.iOS(.v9)],
8 | products: [.library(name: "TagListView",
9 | targets: ["TagListView"])],
10 | targets: [.target(name: "TagListView",
11 | path: "TagListView")],
12 | swiftLanguageVersions: [.v5])
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TagListView
2 |
3 | [](https://travis-ci.org/ElaWorkshop/TagListView)
4 | [](http://cocoadocs.org/docsets/TagListView/)
5 | [](https://github.com/ElaWorkshop/TagListView/blob/master/LICENSE)
6 | [](https://github.com/Carthage/Carthage)
7 |
8 | Simple and highly customizable iOS tag list view, in Swift.
9 |
10 | Supports Storyboard, Auto Layout, and @IBDesignable.
11 |
12 |
13 |
14 | ## Usage
15 |
16 | The most convenient way is to use Storyboard. Drag a view to Storyboard and set Class to `TagListView` (if you use CocoaPods, also set Module to `TagListView`). Then you can play with the attributes in the right pane, and see the preview in real time thanks to [@IBDesignable](http://nshipster.com/ibinspectable-ibdesignable/).
17 |
18 |
19 |
20 | You can add tag to the tag list view, or set custom font and alignment through code:
21 |
22 | ```swift
23 | tagListView.textFont = UIFont.systemFont(ofSize: 24)
24 | tagListView.alignment = .center // possible values are [.leading, .trailing, .left, .center, .right]
25 | tagListView.minWidth = 57
26 |
27 | tagListView.addTag("TagListView")
28 | tagListView.addTags(["Add", "two", "tags"])
29 |
30 | tagListView.insertTag("This should be the second tag", at: 1)
31 |
32 | tagListView.setTitle("New Title", at: 6) // to replace the title a tag
33 |
34 | tagListView.removeTag("meow") // all tags with title “meow” will be removed
35 | tagListView.removeAllTags()
36 | ```
37 |
38 | You can implement `TagListViewDelegate` to receive tag pressed event:
39 |
40 | ```swift
41 | // ...
42 | {
43 | // ...
44 | tagListView.delegate = self
45 | // ...
46 | }
47 |
48 | func tagPressed(title: String, tagView: TagView, sender: TagListView) {
49 | print("Tag pressed: \(title), \(sender)")
50 | }
51 | ```
52 |
53 | You can also customize a particular tag, or set tap handler for it by manipulating the `TagView` object returned by `addTag(_:)`:
54 |
55 | ```swift
56 | let tagView = tagListView.addTag("blue")
57 | tagView.tagBackgroundColor = UIColor.blueColor()
58 | tagView.onTap = { tagView in
59 | print("Don’t tap me!")
60 | }
61 | ```
62 |
63 | Be aware that if you update a property (e.g. `tagBackgroundColor`) for a `TagListView`, all the inner `TagView`s will be updated.
64 |
65 | ## Installation
66 |
67 | Use [CocoaPods](https://github.com/CocoaPods/CocoaPods):
68 |
69 | ```ruby
70 | pod 'TagListView', '~> 1.0'
71 | ```
72 |
73 | Or [Carthage](https://github.com/Carthage/Carthage):
74 |
75 | ```ruby
76 | github "ElaWorkshop/TagListView" ~> 1.0
77 | ```
78 |
79 | Or drag **TagListView** folder into your project.
80 |
81 | ### Older Swift Versions?
82 |
83 | Currently, the `master` branch is using Swift 5.
84 |
85 | For Swift 4, use version [1.3.2](https://github.com/ElaWorkshop/TagListView/releases/tag/1.3.2) or [swift-4](https://github.com/ElaWorkshop/TagListView/tree/swift-4) branch. For Swift 3, use version [1.2.0](https://github.com/ElaWorkshop/TagListView/releases/tag/1.2.0) or [swift-3](https://github.com/ElaWorkshop/TagListView/tree/swift-3) branch. For Swift 2, use version [1.0.1](https://github.com/ElaWorkshop/TagListView/releases/tag/1.0.1) or [swift-2.3](https://github.com/ElaWorkshop/TagListView/tree/swift-2.3) branch. For Swift 1.2, use version [0.2](https://github.com/ElaWorkshop/TagListView/releases/tag/0.2).
86 |
87 | ## Contribution
88 |
89 | Pull requests are welcome! If you want to do something big, please open an issue to let me know first.
90 |
91 | ## License
92 |
93 | MIT
94 |
--------------------------------------------------------------------------------
/Screenshots/InterfaceBuilder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElaWorkshop/TagListView/78e7cec8488c9d0808898203312a7dfdb2a3777a/Screenshots/InterfaceBuilder.png
--------------------------------------------------------------------------------
/Screenshots/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElaWorkshop/TagListView/78e7cec8488c9d0808898203312a7dfdb2a3777a/Screenshots/Screenshot.png
--------------------------------------------------------------------------------
/TagListView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "TagListView"
3 | s.version = "1.4.1"
4 | s.summary = "Simple but highly customizable iOS tag list view, in Swift."
5 | s.homepage = "https://github.com/ElaWorkshop/TagListView"
6 | s.social_media_url = "http://twitter.com/elabuild"
7 |
8 | s.license = "MIT"
9 | s.author = { "LIU Dongyuan" => "liu.dongyuan@gmail.com" }
10 |
11 | s.swift_version = '5.0'
12 |
13 | s.platform = :ios, "11.0"
14 | s.source = { :git => "https://github.com/ElaWorkshop/TagListView.git", :tag => s.version }
15 | s.source_files = "TagListView/*.swift"
16 | s.requires_arc = true
17 | end
18 |
--------------------------------------------------------------------------------
/TagListView/CloseButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CloseButton.swift
3 | // TagListViewDemo
4 | //
5 | // Created by Benjamin Wu on 2/11/16.
6 | // Copyright © 2016 Ela. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal class CloseButton: UIButton {
12 |
13 | var iconSize: CGFloat = 10
14 | var lineWidth: CGFloat = 1
15 | var lineColor: UIColor = UIColor.white.withAlphaComponent(0.54)
16 |
17 | weak var tagView: TagView?
18 |
19 | override func draw(_ rect: CGRect) {
20 | let path = UIBezierPath()
21 |
22 | path.lineWidth = lineWidth
23 | path.lineCapStyle = .round
24 |
25 | let iconFrame = CGRect(
26 | x: (rect.width - iconSize) / 2.0,
27 | y: (rect.height - iconSize) / 2.0,
28 | width: iconSize,
29 | height: iconSize
30 | )
31 |
32 | path.move(to: iconFrame.origin)
33 | path.addLine(to: CGPoint(x: iconFrame.maxX, y: iconFrame.maxY))
34 | path.move(to: CGPoint(x: iconFrame.maxX, y: iconFrame.minY))
35 | path.addLine(to: CGPoint(x: iconFrame.minX, y: iconFrame.maxY))
36 |
37 | lineColor.setStroke()
38 |
39 | path.stroke()
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/TagListView/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/TagListView/TagListView.h:
--------------------------------------------------------------------------------
1 | //
2 | // TagListView.h
3 | // TagListView
4 | //
5 | // Created by MORITANAOKI on 2015/09/11.
6 | // Copyright (c) 2015年 Ela. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for TagListView.
12 | FOUNDATION_EXPORT double TagListViewVersionNumber;
13 |
14 | //! Project version string for TagListView.
15 | FOUNDATION_EXPORT const unsigned char TagListViewVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
--------------------------------------------------------------------------------
/TagListView/TagListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagListView.swift
3 | // TagListViewDemo
4 | //
5 | // Created by Dongyuan Liu on 2015-05-09.
6 | // Copyright (c) 2015 Ela. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @objc public protocol TagListViewDelegate {
12 | @objc optional func tagPressed(_ title: String, tagView: TagView, sender: TagListView) -> Void
13 | @objc optional func tagRemoveButtonPressed(_ title: String, tagView: TagView, sender: TagListView) -> Void
14 | }
15 |
16 | @IBDesignable
17 | open class TagListView: UIView {
18 |
19 | @IBInspectable open dynamic var textColor: UIColor = .white {
20 | didSet {
21 | tagViews.forEach {
22 | $0.textColor = textColor
23 | }
24 | }
25 | }
26 |
27 | @IBInspectable open dynamic var selectedTextColor: UIColor = .white {
28 | didSet {
29 | tagViews.forEach {
30 | $0.selectedTextColor = selectedTextColor
31 | }
32 | }
33 | }
34 |
35 | @IBInspectable open dynamic var tagLineBreakMode: NSLineBreakMode = .byTruncatingMiddle {
36 | didSet {
37 | tagViews.forEach {
38 | $0.titleLineBreakMode = tagLineBreakMode
39 | }
40 | }
41 | }
42 |
43 | @IBInspectable open dynamic var tagBackgroundColor: UIColor = UIColor.gray {
44 | didSet {
45 | tagViews.forEach {
46 | $0.tagBackgroundColor = tagBackgroundColor
47 | }
48 | }
49 | }
50 |
51 | @IBInspectable open dynamic var tagHighlightedBackgroundColor: UIColor? {
52 | didSet {
53 | tagViews.forEach {
54 | $0.highlightedBackgroundColor = tagHighlightedBackgroundColor
55 | }
56 | }
57 | }
58 |
59 | @IBInspectable open dynamic var tagSelectedBackgroundColor: UIColor? {
60 | didSet {
61 | tagViews.forEach {
62 | $0.selectedBackgroundColor = tagSelectedBackgroundColor
63 | }
64 | }
65 | }
66 |
67 | @IBInspectable open dynamic var cornerRadius: CGFloat = 0 {
68 | didSet {
69 | tagViews.forEach {
70 | $0.cornerRadius = cornerRadius
71 | }
72 | }
73 | }
74 | @IBInspectable open dynamic var borderWidth: CGFloat = 0 {
75 | didSet {
76 | tagViews.forEach {
77 | $0.borderWidth = borderWidth
78 | }
79 | }
80 | }
81 |
82 | @IBInspectable open dynamic var borderColor: UIColor? {
83 | didSet {
84 | tagViews.forEach {
85 | $0.borderColor = borderColor
86 | }
87 | }
88 | }
89 |
90 | @IBInspectable open dynamic var selectedBorderColor: UIColor? {
91 | didSet {
92 | tagViews.forEach {
93 | $0.selectedBorderColor = selectedBorderColor
94 | }
95 | }
96 | }
97 |
98 | @IBInspectable open dynamic var paddingY: CGFloat = 2 {
99 | didSet {
100 | defer { rearrangeViews() }
101 | tagViews.forEach {
102 | $0.paddingY = paddingY
103 | }
104 | }
105 | }
106 | @IBInspectable open dynamic var paddingX: CGFloat = 5 {
107 | didSet {
108 | defer { rearrangeViews() }
109 | tagViews.forEach {
110 | $0.paddingX = paddingX
111 | }
112 | }
113 | }
114 | @IBInspectable open dynamic var marginY: CGFloat = 2 {
115 | didSet {
116 | rearrangeViews()
117 | }
118 | }
119 | @IBInspectable open dynamic var marginX: CGFloat = 5 {
120 | didSet {
121 | rearrangeViews()
122 | }
123 | }
124 |
125 | @IBInspectable open dynamic var minWidth: CGFloat = 0 {
126 | didSet {
127 | rearrangeViews()
128 | }
129 | }
130 |
131 | @objc public enum Alignment: Int {
132 | case left
133 | case center
134 | case right
135 | case leading
136 | case trailing
137 | }
138 | @IBInspectable open var alignment: Alignment = .leading {
139 | didSet {
140 | rearrangeViews()
141 | }
142 | }
143 | @IBInspectable open dynamic var shadowColor: UIColor = .white {
144 | didSet {
145 | rearrangeViews()
146 | }
147 | }
148 | @IBInspectable open dynamic var shadowRadius: CGFloat = 0 {
149 | didSet {
150 | rearrangeViews()
151 | }
152 | }
153 | @IBInspectable open dynamic var shadowOffset: CGSize = .zero {
154 | didSet {
155 | rearrangeViews()
156 | }
157 | }
158 | @IBInspectable open dynamic var shadowOpacity: Float = 0 {
159 | didSet {
160 | rearrangeViews()
161 | }
162 | }
163 |
164 | @IBInspectable open dynamic var enableRemoveButton: Bool = false {
165 | didSet {
166 | defer { rearrangeViews() }
167 | tagViews.forEach {
168 | $0.enableRemoveButton = enableRemoveButton
169 | }
170 | }
171 | }
172 |
173 | @IBInspectable open dynamic var removeButtonIconSize: CGFloat = 12 {
174 | didSet {
175 | defer { rearrangeViews() }
176 | tagViews.forEach {
177 | $0.removeButtonIconSize = removeButtonIconSize
178 | }
179 | }
180 | }
181 | @IBInspectable open dynamic var removeIconLineWidth: CGFloat = 1 {
182 | didSet {
183 | defer { rearrangeViews() }
184 | tagViews.forEach {
185 | $0.removeIconLineWidth = removeIconLineWidth
186 | }
187 | }
188 | }
189 |
190 | @IBInspectable open dynamic var removeIconLineColor: UIColor = UIColor.white.withAlphaComponent(0.54) {
191 | didSet {
192 | defer { rearrangeViews() }
193 | tagViews.forEach {
194 | $0.removeIconLineColor = removeIconLineColor
195 | }
196 | }
197 | }
198 |
199 | @objc open dynamic var textFont: UIFont = .systemFont(ofSize: 12) {
200 | didSet {
201 | defer { rearrangeViews() }
202 | tagViews.forEach {
203 | $0.textFont = textFont
204 | }
205 | }
206 | }
207 |
208 | @IBOutlet open weak var delegate: TagListViewDelegate?
209 |
210 | open private(set) var tagViews: [TagView] = []
211 | private(set) var tagBackgroundViews: [UIView] = []
212 | private(set) var rowViews: [UIView] = []
213 | private(set) var tagViewHeight: CGFloat = 0
214 | private(set) var rows = 0 {
215 | didSet {
216 | invalidateIntrinsicContentSize()
217 | }
218 | }
219 |
220 | // MARK: - Interface Builder
221 |
222 | open override func prepareForInterfaceBuilder() {
223 | addTag("Welcome")
224 | addTag("to")
225 | addTag("TagListView").isSelected = true
226 | }
227 |
228 | // MARK: - Layout
229 |
230 | open override func layoutSubviews() {
231 | defer { rearrangeViews() }
232 | super.layoutSubviews()
233 | }
234 |
235 | private func rearrangeViews() {
236 | let views = tagViews as [UIView] + tagBackgroundViews + rowViews
237 | views.forEach {
238 | $0.removeFromSuperview()
239 | }
240 | rowViews.removeAll(keepingCapacity: true)
241 |
242 | var isRtl: Bool = false
243 |
244 | if #available(iOS 10.0, tvOS 10.0, *) {
245 | isRtl = effectiveUserInterfaceLayoutDirection == .rightToLeft
246 | }
247 | else if #available(iOS 9.0, *) {
248 | isRtl = UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft
249 | }
250 | else if let shared = UIApplication.value(forKey: "sharedApplication") as? UIApplication {
251 | isRtl = shared.userInterfaceLayoutDirection == .leftToRight
252 | }
253 |
254 | var alignment = self.alignment
255 |
256 | if alignment == .leading {
257 | alignment = isRtl ? .right : .left
258 | }
259 | else if alignment == .trailing {
260 | alignment = isRtl ? .left : .right
261 | }
262 |
263 | var currentRow = 0
264 | var currentRowView: UIView!
265 | var currentRowTagCount = 0
266 | var currentRowWidth: CGFloat = 0
267 | let frameWidth = frame.width
268 |
269 | let directionTransform = isRtl
270 | ? CGAffineTransform(scaleX: -1.0, y: 1.0)
271 | : CGAffineTransform.identity
272 |
273 | for (index, tagView) in tagViews.enumerated() {
274 | tagView.frame.size = tagView.intrinsicContentSize
275 | tagViewHeight = tagView.frame.height
276 |
277 | if currentRowTagCount == 0 || currentRowWidth + tagView.frame.width > frameWidth {
278 | currentRow += 1
279 | currentRowWidth = 0
280 | currentRowTagCount = 0
281 | currentRowView = UIView()
282 | currentRowView.transform = directionTransform
283 | currentRowView.frame.origin.y = CGFloat(currentRow - 1) * (tagViewHeight + marginY)
284 |
285 | rowViews.append(currentRowView)
286 | addSubview(currentRowView)
287 |
288 | tagView.frame.size.width = min(tagView.frame.size.width, frameWidth)
289 | }
290 |
291 | let tagBackgroundView = tagBackgroundViews[index]
292 | tagBackgroundView.transform = directionTransform
293 | tagBackgroundView.frame.origin = CGPoint(
294 | x: currentRowWidth,
295 | y: 0)
296 | tagBackgroundView.frame.size = tagView.bounds.size
297 | tagView.frame.size.width = max(minWidth, tagView.frame.size.width)
298 | tagBackgroundView.layer.shadowColor = shadowColor.cgColor
299 | tagBackgroundView.layer.shadowPath = UIBezierPath(roundedRect: tagBackgroundView.bounds, cornerRadius: cornerRadius).cgPath
300 | tagBackgroundView.layer.shadowOffset = shadowOffset
301 | tagBackgroundView.layer.shadowOpacity = shadowOpacity
302 | tagBackgroundView.layer.shadowRadius = shadowRadius
303 | tagBackgroundView.addSubview(tagView)
304 | currentRowView.addSubview(tagBackgroundView)
305 |
306 | currentRowTagCount += 1
307 | currentRowWidth += tagView.frame.width + marginX
308 |
309 | switch alignment {
310 | case .leading: fallthrough // switch must be exahutive
311 | case .left:
312 | currentRowView.frame.origin.x = 0
313 | case .center:
314 | currentRowView.frame.origin.x = (frameWidth - (currentRowWidth - marginX)) / 2
315 | case .trailing: fallthrough // switch must be exahutive
316 | case .right:
317 | currentRowView.frame.origin.x = frameWidth - (currentRowWidth - marginX)
318 | }
319 | currentRowView.frame.size.width = currentRowWidth
320 | currentRowView.frame.size.height = max(tagViewHeight, currentRowView.frame.height)
321 | }
322 | rows = currentRow
323 |
324 | invalidateIntrinsicContentSize()
325 | }
326 |
327 | // MARK: - Manage tags
328 |
329 | override open var intrinsicContentSize: CGSize {
330 | var height = CGFloat(rows) * (tagViewHeight + marginY)
331 | if rows > 0 {
332 | height -= marginY
333 | }
334 | return CGSize(width: frame.width, height: height)
335 | }
336 |
337 | private func createNewTagView(_ title: String) -> TagView {
338 | let tagView = TagView(title: title)
339 |
340 | tagView.textColor = textColor
341 | tagView.selectedTextColor = selectedTextColor
342 | tagView.tagBackgroundColor = tagBackgroundColor
343 | tagView.highlightedBackgroundColor = tagHighlightedBackgroundColor
344 | tagView.selectedBackgroundColor = tagSelectedBackgroundColor
345 | tagView.titleLineBreakMode = tagLineBreakMode
346 | tagView.cornerRadius = cornerRadius
347 | tagView.borderWidth = borderWidth
348 | tagView.borderColor = borderColor
349 | tagView.selectedBorderColor = selectedBorderColor
350 | tagView.paddingX = paddingX
351 | tagView.paddingY = paddingY
352 | tagView.textFont = textFont
353 | tagView.removeIconLineWidth = removeIconLineWidth
354 | tagView.removeButtonIconSize = removeButtonIconSize
355 | tagView.enableRemoveButton = enableRemoveButton
356 | tagView.removeIconLineColor = removeIconLineColor
357 | tagView.addTarget(self, action: #selector(tagPressed(_:)), for: .touchUpInside)
358 | tagView.removeButton.addTarget(self, action: #selector(removeButtonPressed(_:)), for: .touchUpInside)
359 |
360 | // On long press, deselect all tags except this one
361 | tagView.onLongPress = { [unowned self] this in
362 | self.tagViews.forEach {
363 | $0.isSelected = $0 == this
364 | }
365 | }
366 |
367 | return tagView
368 | }
369 |
370 | @discardableResult
371 | open func addTag(_ title: String) -> TagView {
372 | defer { rearrangeViews() }
373 | return addTagView(createNewTagView(title))
374 | }
375 |
376 | @discardableResult
377 | open func addTags(_ titles: [String]) -> [TagView] {
378 | return addTagViews(titles.map(createNewTagView))
379 | }
380 |
381 | @discardableResult
382 | open func addTagView(_ tagView: TagView) -> TagView {
383 | defer { rearrangeViews() }
384 | tagViews.append(tagView)
385 | tagBackgroundViews.append(UIView(frame: tagView.bounds))
386 |
387 | return tagView
388 | }
389 |
390 | @discardableResult
391 | open func addTagViews(_ tagViewList: [TagView]) -> [TagView] {
392 | defer { rearrangeViews() }
393 | tagViewList.forEach {
394 | tagViews.append($0)
395 | tagBackgroundViews.append(UIView(frame: $0.bounds))
396 | }
397 | return tagViews
398 | }
399 |
400 | @discardableResult
401 | open func insertTag(_ title: String, at index: Int) -> TagView {
402 | return insertTagView(createNewTagView(title), at: index)
403 | }
404 |
405 |
406 | @discardableResult
407 | open func insertTagView(_ tagView: TagView, at index: Int) -> TagView {
408 | defer { rearrangeViews() }
409 | tagViews.insert(tagView, at: index)
410 | tagBackgroundViews.insert(UIView(frame: tagView.bounds), at: index)
411 |
412 | return tagView
413 | }
414 |
415 | open func setTitle(_ title: String, at index: Int) {
416 | tagViews[index].titleLabel?.text = title
417 | }
418 |
419 | open func removeTag(_ title: String) {
420 | tagViews.reversed().filter({ $0.currentTitle == title }).forEach(removeTagView)
421 | }
422 |
423 | open func removeTagView(_ tagView: TagView) {
424 | defer { rearrangeViews() }
425 |
426 | tagView.removeFromSuperview()
427 | if let index = tagViews.firstIndex(of: tagView) {
428 | tagViews.remove(at: index)
429 | tagBackgroundViews.remove(at: index)
430 | }
431 | }
432 |
433 | open func removeAllTags() {
434 | defer {
435 | tagViews = []
436 | tagBackgroundViews = []
437 | rearrangeViews()
438 | }
439 |
440 | let views: [UIView] = tagViews + tagBackgroundViews
441 | views.forEach { $0.removeFromSuperview() }
442 | }
443 |
444 | open func selectedTags() -> [TagView] {
445 | return tagViews.filter { $0.isSelected }
446 | }
447 |
448 | // MARK: - Events
449 |
450 | @objc func tagPressed(_ sender: TagView!) {
451 | sender.onTap?(sender)
452 | delegate?.tagPressed?(sender.currentTitle ?? "", tagView: sender, sender: self)
453 | }
454 |
455 | @objc func removeButtonPressed(_ closeButton: CloseButton!) {
456 | if let tagView = closeButton.tagView {
457 | delegate?.tagRemoveButtonPressed?(tagView.currentTitle ?? "", tagView: tagView, sender: self)
458 | }
459 | }
460 | }
461 |
--------------------------------------------------------------------------------
/TagListView/TagView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagView.swift
3 | // TagListViewDemo
4 | //
5 | // Created by Dongyuan Liu on 2015-05-09.
6 | // Copyright (c) 2015 Ela. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @IBDesignable
12 | open class TagView: UIButton {
13 |
14 | @IBInspectable open var cornerRadius: CGFloat = 0 {
15 | didSet {
16 | layer.cornerRadius = cornerRadius
17 | layer.masksToBounds = cornerRadius > 0
18 | }
19 | }
20 | @IBInspectable open var borderWidth: CGFloat = 0 {
21 | didSet {
22 | layer.borderWidth = borderWidth
23 | }
24 | }
25 |
26 | @IBInspectable open var borderColor: UIColor? {
27 | didSet {
28 | reloadStyles()
29 | }
30 | }
31 |
32 | @IBInspectable open var textColor: UIColor = UIColor.white {
33 | didSet {
34 | reloadStyles()
35 | }
36 | }
37 | @IBInspectable open var selectedTextColor: UIColor = UIColor.white {
38 | didSet {
39 | reloadStyles()
40 | }
41 | }
42 | @IBInspectable open var titleLineBreakMode: NSLineBreakMode = .byTruncatingMiddle {
43 | didSet {
44 | titleLabel?.lineBreakMode = titleLineBreakMode
45 | }
46 | }
47 | @IBInspectable open var paddingY: CGFloat = 2 {
48 | didSet {
49 | titleEdgeInsets.top = paddingY
50 | titleEdgeInsets.bottom = paddingY
51 | }
52 | }
53 | @IBInspectable open var paddingX: CGFloat = 5 {
54 | didSet {
55 | titleEdgeInsets.left = paddingX
56 | updateRightInsets()
57 | }
58 | }
59 |
60 | @IBInspectable open var tagBackgroundColor: UIColor = UIColor.gray {
61 | didSet {
62 | reloadStyles()
63 | }
64 | }
65 |
66 | @IBInspectable open var highlightedBackgroundColor: UIColor? {
67 | didSet {
68 | reloadStyles()
69 | }
70 | }
71 |
72 | @IBInspectable open var selectedBorderColor: UIColor? {
73 | didSet {
74 | reloadStyles()
75 | }
76 | }
77 |
78 | @IBInspectable open var selectedBackgroundColor: UIColor? {
79 | didSet {
80 | reloadStyles()
81 | }
82 | }
83 |
84 | @IBInspectable open var textFont: UIFont = .systemFont(ofSize: 12) {
85 | didSet {
86 | titleLabel?.font = textFont
87 | }
88 | }
89 |
90 | private func reloadStyles() {
91 | if isHighlighted {
92 | if let highlightedBackgroundColor = highlightedBackgroundColor {
93 | // For highlighted, if it's nil, we should not fallback to backgroundColor.
94 | // Instead, we keep the current color.
95 | backgroundColor = highlightedBackgroundColor
96 | }
97 | }
98 | else if isSelected {
99 | backgroundColor = selectedBackgroundColor ?? tagBackgroundColor
100 | layer.borderColor = selectedBorderColor?.cgColor ?? borderColor?.cgColor
101 | setTitleColor(selectedTextColor, for: UIControl.State())
102 | }
103 | else {
104 | backgroundColor = tagBackgroundColor
105 | layer.borderColor = borderColor?.cgColor
106 | setTitleColor(textColor, for: UIControl.State())
107 | }
108 | }
109 |
110 | override open var isHighlighted: Bool {
111 | didSet {
112 | reloadStyles()
113 | }
114 | }
115 |
116 | override open var isSelected: Bool {
117 | didSet {
118 | reloadStyles()
119 | }
120 | }
121 |
122 | // MARK: remove button
123 |
124 | let removeButton = CloseButton()
125 |
126 | @IBInspectable open var enableRemoveButton: Bool = false {
127 | didSet {
128 | removeButton.isHidden = !enableRemoveButton
129 | updateRightInsets()
130 | }
131 | }
132 |
133 | @IBInspectable open var removeButtonIconSize: CGFloat = 12 {
134 | didSet {
135 | removeButton.iconSize = removeButtonIconSize
136 | updateRightInsets()
137 | }
138 | }
139 |
140 | @IBInspectable open var removeIconLineWidth: CGFloat = 3 {
141 | didSet {
142 | removeButton.lineWidth = removeIconLineWidth
143 | }
144 | }
145 | @IBInspectable open var removeIconLineColor: UIColor = UIColor.white.withAlphaComponent(0.54) {
146 | didSet {
147 | removeButton.lineColor = removeIconLineColor
148 | }
149 | }
150 |
151 | /// Handles Tap (TouchUpInside)
152 | open var onTap: ((TagView) -> Void)?
153 | open var onLongPress: ((TagView) -> Void)?
154 |
155 | // MARK: - init
156 |
157 | required public init?(coder aDecoder: NSCoder) {
158 | super.init(coder: aDecoder)
159 |
160 | setupView()
161 | }
162 |
163 | public init(title: String) {
164 | super.init(frame: CGRect.zero)
165 | setTitle(title, for: UIControl.State())
166 |
167 | setupView()
168 | }
169 |
170 | private func setupView() {
171 | titleLabel?.lineBreakMode = titleLineBreakMode
172 |
173 | frame.size = intrinsicContentSize
174 | addSubview(removeButton)
175 | removeButton.tagView = self
176 |
177 | let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.longPress))
178 | self.addGestureRecognizer(longPress)
179 | }
180 |
181 | @objc func longPress() {
182 | onLongPress?(self)
183 | }
184 |
185 | // MARK: - layout
186 |
187 | override open var intrinsicContentSize: CGSize {
188 | var size = titleLabel?.text?.size(withAttributes: [NSAttributedString.Key.font: textFont]) ?? CGSize.zero
189 | size.height = textFont.pointSize + paddingY * 2
190 | size.width += paddingX * 2
191 | if size.width < size.height {
192 | size.width = size.height
193 | }
194 | if enableRemoveButton {
195 | size.width += removeButtonIconSize + paddingX
196 | }
197 | return size
198 | }
199 |
200 | private func updateRightInsets() {
201 | if enableRemoveButton {
202 | titleEdgeInsets.right = paddingX + removeButtonIconSize + paddingX
203 | }
204 | else {
205 | titleEdgeInsets.right = paddingX
206 | }
207 | }
208 |
209 | open override func layoutSubviews() {
210 | super.layoutSubviews()
211 | if enableRemoveButton {
212 | removeButton.frame.size.width = paddingX + removeButtonIconSize + paddingX
213 | removeButton.frame.origin.x = self.frame.width - removeButton.frame.width
214 | removeButton.frame.size.height = self.frame.height
215 | removeButton.frame.origin.y = 0
216 | }
217 | }
218 | }
219 |
220 | /// Swift < 4.2 support
221 | #if !(swift(>=4.2))
222 | private extension NSAttributedString {
223 | typealias Key = NSAttributedStringKey
224 | }
225 | private extension UIControl {
226 | typealias State = UIControlState
227 | }
228 | #endif
229 |
--------------------------------------------------------------------------------
/TagListViewDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1A6076BF1AFED303009EA00E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6076BE1AFED303009EA00E /* AppDelegate.swift */; };
11 | 1A6076C11AFED303009EA00E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6076C01AFED303009EA00E /* ViewController.swift */; };
12 | 1A6076C41AFED303009EA00E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A6076C21AFED303009EA00E /* Main.storyboard */; };
13 | 1A6076C61AFED303009EA00E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A6076C51AFED303009EA00E /* Images.xcassets */; };
14 | 1A6076C91AFED303009EA00E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1A6076C71AFED303009EA00E /* LaunchScreen.xib */; };
15 | 1A6076DF1AFED38F009EA00E /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6076DE1AFED38F009EA00E /* TagView.swift */; };
16 | 1A6076E31AFED866009EA00E /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6076E21AFED866009EA00E /* TagListView.swift */; };
17 | 1AD1770B1B6D73E70081F615 /* TagListView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AD176F41B6D73E70081F615 /* TagListView.framework */; };
18 | 1AD1770C1B6D73E70081F615 /* TagListView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1AD176F41B6D73E70081F615 /* TagListView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
19 | 1AD177141B6D74570081F615 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6076DE1AFED38F009EA00E /* TagView.swift */; };
20 | 1AD177151B6D745B0081F615 /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6076E21AFED866009EA00E /* TagListView.swift */; };
21 | 90A878531BA1EBA1001C8989 /* TagListView.h in Headers */ = {isa = PBXBuildFile; fileRef = 90A878521BA1EBA1001C8989 /* TagListView.h */; settings = {ATTRIBUTES = (Public, ); }; };
22 | FFBD46391C6E1AA60045684A /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBD46381C6E1AA60045684A /* CloseButton.swift */; };
23 | FFBD463A1C6E1AB50045684A /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBD46381C6E1AA60045684A /* CloseButton.swift */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXContainerItemProxy section */
27 | 1AD177091B6D73E70081F615 /* PBXContainerItemProxy */ = {
28 | isa = PBXContainerItemProxy;
29 | containerPortal = 1A6076B11AFED303009EA00E /* Project object */;
30 | proxyType = 1;
31 | remoteGlobalIDString = 1AD176F31B6D73E70081F615;
32 | remoteInfo = TagListView;
33 | };
34 | /* End PBXContainerItemProxy section */
35 |
36 | /* Begin PBXCopyFilesBuildPhase section */
37 | 1AD177121B6D73E70081F615 /* Embed Frameworks */ = {
38 | isa = PBXCopyFilesBuildPhase;
39 | buildActionMask = 2147483647;
40 | dstPath = "";
41 | dstSubfolderSpec = 10;
42 | files = (
43 | 1AD1770C1B6D73E70081F615 /* TagListView.framework in Embed Frameworks */,
44 | );
45 | name = "Embed Frameworks";
46 | runOnlyForDeploymentPostprocessing = 0;
47 | };
48 | /* End PBXCopyFilesBuildPhase section */
49 |
50 | /* Begin PBXFileReference section */
51 | 1A6076B91AFED303009EA00E /* TagListViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TagListViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
52 | 1A6076BD1AFED303009EA00E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | 1A6076BE1AFED303009EA00E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
54 | 1A6076C01AFED303009EA00E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
55 | 1A6076C31AFED303009EA00E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
56 | 1A6076C51AFED303009EA00E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
57 | 1A6076C81AFED303009EA00E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
58 | 1A6076DE1AFED38F009EA00E /* TagView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TagView.swift; path = TagListView/TagView.swift; sourceTree = SOURCE_ROOT; };
59 | 1A6076E21AFED866009EA00E /* TagListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TagListView.swift; path = TagListView/TagListView.swift; sourceTree = SOURCE_ROOT; };
60 | 1AD176F41B6D73E70081F615 /* TagListView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TagListView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
61 | 1AD176F71B6D73E70081F615 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
62 | 90A878521BA1EBA1001C8989 /* TagListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagListView.h; sourceTree = ""; };
63 | FFBD46381C6E1AA60045684A /* CloseButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CloseButton.swift; path = ../TagListView/CloseButton.swift; sourceTree = ""; };
64 | /* End PBXFileReference section */
65 |
66 | /* Begin PBXFrameworksBuildPhase section */
67 | 1A6076B61AFED303009EA00E /* Frameworks */ = {
68 | isa = PBXFrameworksBuildPhase;
69 | buildActionMask = 2147483647;
70 | files = (
71 | 1AD1770B1B6D73E70081F615 /* TagListView.framework in Frameworks */,
72 | );
73 | runOnlyForDeploymentPostprocessing = 0;
74 | };
75 | 1AD176F01B6D73E70081F615 /* Frameworks */ = {
76 | isa = PBXFrameworksBuildPhase;
77 | buildActionMask = 2147483647;
78 | files = (
79 | );
80 | runOnlyForDeploymentPostprocessing = 0;
81 | };
82 | /* End PBXFrameworksBuildPhase section */
83 |
84 | /* Begin PBXGroup section */
85 | 1A6076B01AFED303009EA00E = {
86 | isa = PBXGroup;
87 | children = (
88 | 1A6076BB1AFED303009EA00E /* TagListViewDemo */,
89 | 1AD176F51B6D73E70081F615 /* TagListView */,
90 | 1A6076BA1AFED303009EA00E /* Products */,
91 | );
92 | sourceTree = "";
93 | };
94 | 1A6076BA1AFED303009EA00E /* Products */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 1A6076B91AFED303009EA00E /* TagListViewDemo.app */,
98 | 1AD176F41B6D73E70081F615 /* TagListView.framework */,
99 | );
100 | name = Products;
101 | sourceTree = "";
102 | };
103 | 1A6076BB1AFED303009EA00E /* TagListViewDemo */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 1A6076BE1AFED303009EA00E /* AppDelegate.swift */,
107 | 1A6076C01AFED303009EA00E /* ViewController.swift */,
108 | 1A6076E41AFED91A009EA00E /* TagListView */,
109 | 1A6076C21AFED303009EA00E /* Main.storyboard */,
110 | 1A6076C51AFED303009EA00E /* Images.xcassets */,
111 | 1A6076C71AFED303009EA00E /* LaunchScreen.xib */,
112 | 1A6076BC1AFED303009EA00E /* Supporting Files */,
113 | );
114 | path = TagListViewDemo;
115 | sourceTree = "";
116 | };
117 | 1A6076BC1AFED303009EA00E /* Supporting Files */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 1A6076BD1AFED303009EA00E /* Info.plist */,
121 | );
122 | name = "Supporting Files";
123 | sourceTree = "";
124 | };
125 | 1A6076E41AFED91A009EA00E /* TagListView */ = {
126 | isa = PBXGroup;
127 | children = (
128 | 1A6076DE1AFED38F009EA00E /* TagView.swift */,
129 | 1A6076E21AFED866009EA00E /* TagListView.swift */,
130 | FFBD46381C6E1AA60045684A /* CloseButton.swift */,
131 | );
132 | name = TagListView;
133 | sourceTree = "";
134 | };
135 | 1AD176F51B6D73E70081F615 /* TagListView */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 90A878521BA1EBA1001C8989 /* TagListView.h */,
139 | 1AD176F61B6D73E70081F615 /* Supporting Files */,
140 | );
141 | path = TagListView;
142 | sourceTree = "";
143 | };
144 | 1AD176F61B6D73E70081F615 /* Supporting Files */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 1AD176F71B6D73E70081F615 /* Info.plist */,
148 | );
149 | name = "Supporting Files";
150 | sourceTree = "";
151 | };
152 | /* End PBXGroup section */
153 |
154 | /* Begin PBXHeadersBuildPhase section */
155 | 1AD176F11B6D73E70081F615 /* Headers */ = {
156 | isa = PBXHeadersBuildPhase;
157 | buildActionMask = 2147483647;
158 | files = (
159 | 90A878531BA1EBA1001C8989 /* TagListView.h in Headers */,
160 | );
161 | runOnlyForDeploymentPostprocessing = 0;
162 | };
163 | /* End PBXHeadersBuildPhase section */
164 |
165 | /* Begin PBXNativeTarget section */
166 | 1A6076B81AFED303009EA00E /* TagListViewDemo */ = {
167 | isa = PBXNativeTarget;
168 | buildConfigurationList = 1A6076D81AFED303009EA00E /* Build configuration list for PBXNativeTarget "TagListViewDemo" */;
169 | buildPhases = (
170 | 1A6076B51AFED303009EA00E /* Sources */,
171 | 1A6076B61AFED303009EA00E /* Frameworks */,
172 | 1A6076B71AFED303009EA00E /* Resources */,
173 | 1AD177121B6D73E70081F615 /* Embed Frameworks */,
174 | );
175 | buildRules = (
176 | );
177 | dependencies = (
178 | 1AD1770A1B6D73E70081F615 /* PBXTargetDependency */,
179 | );
180 | name = TagListViewDemo;
181 | productName = TagListViewDemo;
182 | productReference = 1A6076B91AFED303009EA00E /* TagListViewDemo.app */;
183 | productType = "com.apple.product-type.application";
184 | };
185 | 1AD176F31B6D73E70081F615 /* TagListView */ = {
186 | isa = PBXNativeTarget;
187 | buildConfigurationList = 1AD177111B6D73E70081F615 /* Build configuration list for PBXNativeTarget "TagListView" */;
188 | buildPhases = (
189 | 1AD176EF1B6D73E70081F615 /* Sources */,
190 | 1AD176F01B6D73E70081F615 /* Frameworks */,
191 | 1AD176F11B6D73E70081F615 /* Headers */,
192 | 1AD176F21B6D73E70081F615 /* Resources */,
193 | );
194 | buildRules = (
195 | );
196 | dependencies = (
197 | );
198 | name = TagListView;
199 | productName = TagListView;
200 | productReference = 1AD176F41B6D73E70081F615 /* TagListView.framework */;
201 | productType = "com.apple.product-type.framework";
202 | };
203 | /* End PBXNativeTarget section */
204 |
205 | /* Begin PBXProject section */
206 | 1A6076B11AFED303009EA00E /* Project object */ = {
207 | isa = PBXProject;
208 | attributes = {
209 | LastSwiftUpdateCheck = 0700;
210 | LastUpgradeCheck = 1020;
211 | ORGANIZATIONNAME = Ela;
212 | TargetAttributes = {
213 | 1A6076B81AFED303009EA00E = {
214 | CreatedOnToolsVersion = 6.3.1;
215 | LastSwiftMigration = 1020;
216 | };
217 | 1AD176F31B6D73E70081F615 = {
218 | CreatedOnToolsVersion = 6.4;
219 | LastSwiftMigration = 1020;
220 | };
221 | };
222 | };
223 | buildConfigurationList = 1A6076B41AFED303009EA00E /* Build configuration list for PBXProject "TagListViewDemo" */;
224 | compatibilityVersion = "Xcode 3.2";
225 | developmentRegion = en;
226 | hasScannedForEncodings = 0;
227 | knownRegions = (
228 | en,
229 | Base,
230 | );
231 | mainGroup = 1A6076B01AFED303009EA00E;
232 | productRefGroup = 1A6076BA1AFED303009EA00E /* Products */;
233 | projectDirPath = "";
234 | projectRoot = "";
235 | targets = (
236 | 1A6076B81AFED303009EA00E /* TagListViewDemo */,
237 | 1AD176F31B6D73E70081F615 /* TagListView */,
238 | );
239 | };
240 | /* End PBXProject section */
241 |
242 | /* Begin PBXResourcesBuildPhase section */
243 | 1A6076B71AFED303009EA00E /* Resources */ = {
244 | isa = PBXResourcesBuildPhase;
245 | buildActionMask = 2147483647;
246 | files = (
247 | 1A6076C41AFED303009EA00E /* Main.storyboard in Resources */,
248 | 1A6076C91AFED303009EA00E /* LaunchScreen.xib in Resources */,
249 | 1A6076C61AFED303009EA00E /* Images.xcassets in Resources */,
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | };
253 | 1AD176F21B6D73E70081F615 /* Resources */ = {
254 | isa = PBXResourcesBuildPhase;
255 | buildActionMask = 2147483647;
256 | files = (
257 | );
258 | runOnlyForDeploymentPostprocessing = 0;
259 | };
260 | /* End PBXResourcesBuildPhase section */
261 |
262 | /* Begin PBXSourcesBuildPhase section */
263 | 1A6076B51AFED303009EA00E /* Sources */ = {
264 | isa = PBXSourcesBuildPhase;
265 | buildActionMask = 2147483647;
266 | files = (
267 | FFBD46391C6E1AA60045684A /* CloseButton.swift in Sources */,
268 | 1A6076C11AFED303009EA00E /* ViewController.swift in Sources */,
269 | 1A6076BF1AFED303009EA00E /* AppDelegate.swift in Sources */,
270 | 1A6076DF1AFED38F009EA00E /* TagView.swift in Sources */,
271 | 1A6076E31AFED866009EA00E /* TagListView.swift in Sources */,
272 | );
273 | runOnlyForDeploymentPostprocessing = 0;
274 | };
275 | 1AD176EF1B6D73E70081F615 /* Sources */ = {
276 | isa = PBXSourcesBuildPhase;
277 | buildActionMask = 2147483647;
278 | files = (
279 | FFBD463A1C6E1AB50045684A /* CloseButton.swift in Sources */,
280 | 1AD177141B6D74570081F615 /* TagView.swift in Sources */,
281 | 1AD177151B6D745B0081F615 /* TagListView.swift in Sources */,
282 | );
283 | runOnlyForDeploymentPostprocessing = 0;
284 | };
285 | /* End PBXSourcesBuildPhase section */
286 |
287 | /* Begin PBXTargetDependency section */
288 | 1AD1770A1B6D73E70081F615 /* PBXTargetDependency */ = {
289 | isa = PBXTargetDependency;
290 | target = 1AD176F31B6D73E70081F615 /* TagListView */;
291 | targetProxy = 1AD177091B6D73E70081F615 /* PBXContainerItemProxy */;
292 | };
293 | /* End PBXTargetDependency section */
294 |
295 | /* Begin PBXVariantGroup section */
296 | 1A6076C21AFED303009EA00E /* Main.storyboard */ = {
297 | isa = PBXVariantGroup;
298 | children = (
299 | 1A6076C31AFED303009EA00E /* Base */,
300 | );
301 | name = Main.storyboard;
302 | sourceTree = "";
303 | };
304 | 1A6076C71AFED303009EA00E /* LaunchScreen.xib */ = {
305 | isa = PBXVariantGroup;
306 | children = (
307 | 1A6076C81AFED303009EA00E /* Base */,
308 | );
309 | name = LaunchScreen.xib;
310 | sourceTree = "";
311 | };
312 | /* End PBXVariantGroup section */
313 |
314 | /* Begin XCBuildConfiguration section */
315 | 1A6076D61AFED303009EA00E /* Debug */ = {
316 | isa = XCBuildConfiguration;
317 | buildSettings = {
318 | ALWAYS_SEARCH_USER_PATHS = NO;
319 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
320 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
321 | CLANG_CXX_LIBRARY = "libc++";
322 | CLANG_ENABLE_MODULES = YES;
323 | CLANG_ENABLE_OBJC_ARC = YES;
324 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
325 | CLANG_WARN_BOOL_CONVERSION = YES;
326 | CLANG_WARN_COMMA = YES;
327 | CLANG_WARN_CONSTANT_CONVERSION = YES;
328 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
330 | CLANG_WARN_EMPTY_BODY = YES;
331 | CLANG_WARN_ENUM_CONVERSION = YES;
332 | CLANG_WARN_INFINITE_RECURSION = YES;
333 | CLANG_WARN_INT_CONVERSION = YES;
334 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
335 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
336 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
338 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
339 | CLANG_WARN_STRICT_PROTOTYPES = YES;
340 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
341 | CLANG_WARN_UNREACHABLE_CODE = YES;
342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
344 | COPY_PHASE_STRIP = NO;
345 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
346 | ENABLE_STRICT_OBJC_MSGSEND = YES;
347 | ENABLE_TESTABILITY = YES;
348 | GCC_C_LANGUAGE_STANDARD = gnu99;
349 | GCC_DYNAMIC_NO_PIC = NO;
350 | GCC_NO_COMMON_BLOCKS = YES;
351 | GCC_OPTIMIZATION_LEVEL = 0;
352 | GCC_PREPROCESSOR_DEFINITIONS = (
353 | "DEBUG=1",
354 | "$(inherited)",
355 | );
356 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
357 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
358 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
359 | GCC_WARN_UNDECLARED_SELECTOR = YES;
360 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
361 | GCC_WARN_UNUSED_FUNCTION = YES;
362 | GCC_WARN_UNUSED_VARIABLE = YES;
363 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
364 | MTL_ENABLE_DEBUG_INFO = YES;
365 | ONLY_ACTIVE_ARCH = YES;
366 | SDKROOT = iphoneos;
367 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
368 | SWIFT_VERSION = 4.2;
369 | TARGETED_DEVICE_FAMILY = "1,2";
370 | };
371 | name = Debug;
372 | };
373 | 1A6076D71AFED303009EA00E /* Release */ = {
374 | isa = XCBuildConfiguration;
375 | buildSettings = {
376 | ALWAYS_SEARCH_USER_PATHS = NO;
377 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
379 | CLANG_CXX_LIBRARY = "libc++";
380 | CLANG_ENABLE_MODULES = YES;
381 | CLANG_ENABLE_OBJC_ARC = YES;
382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
383 | CLANG_WARN_BOOL_CONVERSION = YES;
384 | CLANG_WARN_COMMA = YES;
385 | CLANG_WARN_CONSTANT_CONVERSION = YES;
386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
388 | CLANG_WARN_EMPTY_BODY = YES;
389 | CLANG_WARN_ENUM_CONVERSION = YES;
390 | CLANG_WARN_INFINITE_RECURSION = YES;
391 | CLANG_WARN_INT_CONVERSION = YES;
392 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
393 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
396 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
397 | CLANG_WARN_STRICT_PROTOTYPES = YES;
398 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
399 | CLANG_WARN_UNREACHABLE_CODE = YES;
400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
401 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
402 | COPY_PHASE_STRIP = NO;
403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
404 | ENABLE_NS_ASSERTIONS = NO;
405 | ENABLE_STRICT_OBJC_MSGSEND = YES;
406 | GCC_C_LANGUAGE_STANDARD = gnu99;
407 | GCC_NO_COMMON_BLOCKS = YES;
408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
410 | GCC_WARN_UNDECLARED_SELECTOR = YES;
411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
412 | GCC_WARN_UNUSED_FUNCTION = YES;
413 | GCC_WARN_UNUSED_VARIABLE = YES;
414 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
415 | MTL_ENABLE_DEBUG_INFO = NO;
416 | SDKROOT = iphoneos;
417 | SWIFT_VERSION = 4.2;
418 | TARGETED_DEVICE_FAMILY = "1,2";
419 | VALIDATE_PRODUCT = YES;
420 | };
421 | name = Release;
422 | };
423 | 1A6076D91AFED303009EA00E /* Debug */ = {
424 | isa = XCBuildConfiguration;
425 | buildSettings = {
426 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
427 | INFOPLIST_FILE = TagListViewDemo/Info.plist;
428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
429 | PRODUCT_BUNDLE_IDENTIFIER = "build.ela.$(PRODUCT_NAME:rfc1034identifier)";
430 | PRODUCT_NAME = "$(TARGET_NAME)";
431 | SWIFT_VERSION = 5.0;
432 | };
433 | name = Debug;
434 | };
435 | 1A6076DA1AFED303009EA00E /* Release */ = {
436 | isa = XCBuildConfiguration;
437 | buildSettings = {
438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
439 | INFOPLIST_FILE = TagListViewDemo/Info.plist;
440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
441 | PRODUCT_BUNDLE_IDENTIFIER = "build.ela.$(PRODUCT_NAME:rfc1034identifier)";
442 | PRODUCT_NAME = "$(TARGET_NAME)";
443 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
444 | SWIFT_VERSION = 5.0;
445 | };
446 | name = Release;
447 | };
448 | 1AD1770D1B6D73E70081F615 /* Debug */ = {
449 | isa = XCBuildConfiguration;
450 | buildSettings = {
451 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
452 | CURRENT_PROJECT_VERSION = 1;
453 | DEFINES_MODULE = YES;
454 | DYLIB_COMPATIBILITY_VERSION = 1;
455 | DYLIB_CURRENT_VERSION = 1;
456 | DYLIB_INSTALL_NAME_BASE = "@rpath";
457 | GCC_PREPROCESSOR_DEFINITIONS = (
458 | "DEBUG=1",
459 | "$(inherited)",
460 | );
461 | INFOPLIST_FILE = TagListView/Info.plist;
462 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
464 | PRODUCT_BUNDLE_IDENTIFIER = "build.ela.$(PRODUCT_NAME:rfc1034identifier)";
465 | PRODUCT_NAME = "$(TARGET_NAME)";
466 | SKIP_INSTALL = YES;
467 | SWIFT_VERSION = 5.0;
468 | VERSIONING_SYSTEM = "apple-generic";
469 | VERSION_INFO_PREFIX = "";
470 | };
471 | name = Debug;
472 | };
473 | 1AD1770E1B6D73E70081F615 /* Release */ = {
474 | isa = XCBuildConfiguration;
475 | buildSettings = {
476 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
477 | CURRENT_PROJECT_VERSION = 1;
478 | DEFINES_MODULE = YES;
479 | DYLIB_COMPATIBILITY_VERSION = 1;
480 | DYLIB_CURRENT_VERSION = 1;
481 | DYLIB_INSTALL_NAME_BASE = "@rpath";
482 | INFOPLIST_FILE = TagListView/Info.plist;
483 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
484 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
485 | PRODUCT_BUNDLE_IDENTIFIER = "build.ela.$(PRODUCT_NAME:rfc1034identifier)";
486 | PRODUCT_NAME = "$(TARGET_NAME)";
487 | SKIP_INSTALL = YES;
488 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
489 | SWIFT_VERSION = 5.0;
490 | VERSIONING_SYSTEM = "apple-generic";
491 | VERSION_INFO_PREFIX = "";
492 | };
493 | name = Release;
494 | };
495 | /* End XCBuildConfiguration section */
496 |
497 | /* Begin XCConfigurationList section */
498 | 1A6076B41AFED303009EA00E /* Build configuration list for PBXProject "TagListViewDemo" */ = {
499 | isa = XCConfigurationList;
500 | buildConfigurations = (
501 | 1A6076D61AFED303009EA00E /* Debug */,
502 | 1A6076D71AFED303009EA00E /* Release */,
503 | );
504 | defaultConfigurationIsVisible = 0;
505 | defaultConfigurationName = Release;
506 | };
507 | 1A6076D81AFED303009EA00E /* Build configuration list for PBXNativeTarget "TagListViewDemo" */ = {
508 | isa = XCConfigurationList;
509 | buildConfigurations = (
510 | 1A6076D91AFED303009EA00E /* Debug */,
511 | 1A6076DA1AFED303009EA00E /* Release */,
512 | );
513 | defaultConfigurationIsVisible = 0;
514 | defaultConfigurationName = Release;
515 | };
516 | 1AD177111B6D73E70081F615 /* Build configuration list for PBXNativeTarget "TagListView" */ = {
517 | isa = XCConfigurationList;
518 | buildConfigurations = (
519 | 1AD1770D1B6D73E70081F615 /* Debug */,
520 | 1AD1770E1B6D73E70081F615 /* Release */,
521 | );
522 | defaultConfigurationIsVisible = 0;
523 | defaultConfigurationName = Release;
524 | };
525 | /* End XCConfigurationList section */
526 | };
527 | rootObject = 1A6076B11AFED303009EA00E /* Project object */;
528 | }
529 |
--------------------------------------------------------------------------------
/TagListViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TagListViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TagListViewDemo.xcodeproj/xcshareddata/xcschemes/TagListView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/TagListViewDemo.xcodeproj/xcshareddata/xcschemes/TagListViewDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
71 |
77 |
78 |
79 |
80 |
81 |
87 |
88 |
89 |
90 |
91 |
92 |
102 |
104 |
110 |
111 |
112 |
113 |
114 |
115 |
121 |
123 |
129 |
130 |
131 |
132 |
134 |
135 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/TagListViewDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TagListViewDemo
4 | //
5 | // Created by Dongyuan Liu on 2015-05-09.
6 | // Copyright (c) 2015 Ela. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/TagListViewDemo/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/TagListViewDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/TagListViewDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/TagListViewDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/TagListViewDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TagListViewDemo
4 | //
5 | // Created by Dongyuan Liu on 2015-05-09.
6 | // Copyright (c) 2015 Ela. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController, TagListViewDelegate {
12 |
13 | @IBOutlet weak var tagListView: TagListView!
14 | @IBOutlet weak var biggerTagListView: TagListView!
15 | @IBOutlet weak var biggestTagListView: TagListView!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | tagListView.delegate = self
21 | tagListView.addTag("TagListView")
22 | tagListView.addTag("TEAChart")
23 | tagListView.addTag("To Be Removed")
24 | tagListView.addTag("To Be Removed")
25 | tagListView.addTag("Quark Shell")
26 | tagListView.removeTag("To Be Removed")
27 | tagListView.addTag("On tap will be removed").onTap = { [weak self] tagView in
28 | self?.tagListView.removeTagView(tagView)
29 | }
30 |
31 | let tagView = tagListView.addTag("gray")
32 | tagView.tagBackgroundColor = UIColor.gray
33 | tagView.onTap = { tagView in
34 | print("Don’t tap me!")
35 | }
36 |
37 | tagListView.insertTag("This should be the third tag", at: 2)
38 |
39 | biggerTagListView.delegate = self
40 | biggerTagListView.textFont = .systemFont(ofSize: 15)
41 | biggerTagListView.shadowRadius = 2
42 | biggerTagListView.shadowOpacity = 0.4
43 | biggerTagListView.shadowColor = UIColor.black
44 | biggerTagListView.shadowOffset = CGSize(width: 1, height: 1)
45 | biggerTagListView.addTag("Inboard")
46 | biggerTagListView.addTag("Pomotodo")
47 | biggerTagListView.addTag("Halo Word")
48 | biggerTagListView.alignment = .center
49 |
50 | biggestTagListView.delegate = self
51 | biggestTagListView.textFont = .systemFont(ofSize: 24)
52 | // it is also possible to add all tags in one go
53 | biggestTagListView.addTags(["all", "your", "tag", "are", "belong", "to", "us"])
54 | biggestTagListView.minWidth = 57
55 | biggestTagListView.alignment = .right
56 |
57 | }
58 |
59 | override func didReceiveMemoryWarning() {
60 | super.didReceiveMemoryWarning()
61 | // Dispose of any resources that can be recreated.
62 | }
63 |
64 | // MARK: TagListViewDelegate
65 | func tagPressed(_ title: String, tagView: TagView, sender: TagListView) {
66 | print("Tag pressed: \(title), \(sender)")
67 | tagView.isSelected = !tagView.isSelected
68 | }
69 |
70 | func tagRemoveButtonPressed(_ title: String, tagView: TagView, sender: TagListView) {
71 | print("Tag Remove pressed: \(title), \(sender)")
72 | sender.removeTagView(tagView)
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------