├── .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 | [![Travis CI](https://travis-ci.org/ElaWorkshop/TagListView.svg)](https://travis-ci.org/ElaWorkshop/TagListView) 4 | [![Version](https://img.shields.io/cocoapods/v/TagListView.svg?style=flat)](http://cocoadocs.org/docsets/TagListView/) 5 | [![License](https://img.shields.io/cocoapods/l/TagListView.svg?style=flat)](https://github.com/ElaWorkshop/TagListView/blob/master/LICENSE) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | Screenshot 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 | Interface Builder 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 | --------------------------------------------------------------------------------