├── .gitignore ├── Demo ├── InTabViewDemo │ ├── InTabViewVC.swift │ └── TableViewCell.swift └── InViewDemo │ └── InViewDemoVC.swift ├── Examples ├── Base.lproj │ └── Main.storyboard ├── ExamplesViewController.swift └── LJTagsView-Bridging-Header.h ├── LICENSE ├── LJTagsView.podspec ├── LJTagsView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── ims_mac.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── ims_mac.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── LJTagsView ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── select_not.imageset │ │ ├── Contents.json │ │ ├── select_not@2x.png │ │ └── select_not@3x.png │ └── selected.imageset │ │ ├── Contents.json │ │ ├── selected@2x.png │ │ └── selected@3x.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── LJTagsView │ ├── LJTagsView.bundle │ │ └── arrow@2x.png │ └── LJTagsView.swift └── SceneDelegate.swift ├── Podfile ├── README.md └── image └── ezgif-2-923de88307ee.gif /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | Podfile.lock 3 | LJTagsView.xcworkspace -------------------------------------------------------------------------------- /Demo/InTabViewDemo/InTabViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InTabViewVC.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/4/9. 6 | // 7 | 8 | import UIKit 9 | 10 | class UIModel { 11 | var title: String = "" 12 | var padding: CGFloat = 10.0 13 | var isSelect = false 14 | var list: [String]? 15 | } 16 | 17 | class InTabViewVC: UITableViewController { 18 | 19 | var list = Array() 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | self.tableView.register(TableViewCell.self, forCellReuseIdentifier: "reuseIdentifier") 24 | self.tableView.estimatedRowHeight = 100 25 | self.tableView?.rowHeight = UITableView.automaticDimension 26 | for index in 0..<100 { 27 | let model = UIModel() 28 | model.title = "title + \(index)" 29 | model.padding = CGFloat(10 + Double(index)*10) 30 | model.list = ["index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)","index + \(index)"] 31 | list.append(model) 32 | } 33 | 34 | self.tableView.reloadData() 35 | } 36 | 37 | // MARK: - Table view data source 38 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 39 | // #warning Incomplete implementation, return the number of rows 40 | return list.count 41 | } 42 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 43 | 44 | let cell:TableViewCell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell 45 | 46 | cell.model = (list[indexPath.row] as! UIModel) 47 | 48 | return cell 49 | } 50 | 51 | 52 | } 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Demo/InTabViewDemo/TableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCell.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/3/8. 6 | // 7 | 8 | import UIKit 9 | 10 | class TableViewCell: UITableViewCell,LJTagsViewProtocol { 11 | 12 | var model: UIModel? { 13 | willSet { 14 | if let model = newValue { 15 | titleLabel.text = model.title 16 | tagsView.dataSource = model.list! 17 | tagsView.isSelect = model.isSelect 18 | self.tagsView.reloadData() 19 | } 20 | } 21 | } 22 | 23 | required init?(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | lazy var titleLabel: UILabel = UILabel() 28 | lazy var tagsView: LJTagsView = LJTagsView() 29 | lazy var bgView: UIView = UIView() 30 | 31 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 32 | super.init(style: style, reuseIdentifier: reuseIdentifier) 33 | 34 | print(self.contentView.frame.size.width) 35 | /** 必须给content width 值,否则会获取320*/ 36 | self.contentView.frame.size.width = UIScreen.main.bounds.width 37 | print(self.contentView.frame.size.width) 38 | 39 | titleLabel.font = .systemFont(ofSize: 14) 40 | titleLabel.textColor = .black 41 | titleLabel.text = "title" 42 | self.contentView .addSubview(titleLabel); 43 | titleLabel.snp.makeConstraints { (make) in 44 | make.top.left.equalToSuperview().offset(10) 45 | } 46 | 47 | bgView.backgroundColor = .white 48 | bgView.layer.borderWidth = 0.5 49 | bgView.layer.borderColor = UIColor.gray.cgColor 50 | bgView.layer.masksToBounds = true 51 | bgView.layer.cornerRadius = 4 52 | self.contentView .addSubview(bgView) 53 | bgView.snp.makeConstraints { (make) in 54 | make.top.equalTo(titleLabel) 55 | make.right.equalToSuperview().offset(-10) 56 | make.left.equalTo(titleLabel.snp.right).offset(10) 57 | make.bottom.equalToSuperview().offset(-10) 58 | } 59 | 60 | tagsView.tagsViewDelegate = self 61 | tagsView.tagsViewContentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 62 | tagsView.tagsViewMinHeight = 40 63 | bgView.addSubview(tagsView) 64 | tagsView.snp.makeConstraints { (make) in 65 | make.top.right.left.bottom.equalToSuperview() 66 | } 67 | tagsView.showLine = 2 68 | } 69 | 70 | func tagsViewItemTapAction(_ tagsView: LJTagsView, item: TagsPropertyModel, index: NSInteger) { 71 | print("text = \(item.titleLabel.text!) , index = \(index)") 72 | model?.list?.remove(at: index) 73 | tagsView.dataSource.remove(at: index) 74 | 75 | let tab:UITableView = self.superview as! UITableView 76 | tab.reloadData() 77 | 78 | } 79 | 80 | func tagsViewTapAction(_ tagsView: LJTagsView) { 81 | let tab:UITableView = self.superview as! UITableView 82 | model?.isSelect = !(model?.isSelect)! 83 | tab.reloadData() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Demo/InViewDemo/InViewDemoVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InViewDemoVC.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/4/9. 6 | // 7 | 8 | import UIKit 9 | 10 | let k_screenH = UIScreen.main.bounds.height 11 | let k_screenW = UIScreen.main.bounds.width 12 | 13 | class InViewDemoVC: UIViewController { 14 | 15 | enum TagsViewType: Int { 16 | case tagsViewFrameLayout = 0 17 | case tagsViewChangeScrollDirection 18 | case tagsViewShowLine 19 | case tagsViewManySelect 20 | } 21 | 22 | lazy var tagsView = LJTagsView() 23 | lazy var manySelectedResultTagsView = LJTagsView() 24 | 25 | var type: TagsViewType = TagsViewType.tagsViewFrameLayout 26 | 27 | var dataSource = ["Listing ID","Tower","Sole Agency Type","Have Keys","New Development","Tags","Big landlord","Street Address","Currency","Price per unit","Price per unit(Gross)","Price per unit(Saleable)","Size(Gross)","Size(Saleable)","Status","Register","Landlord","SSD","Agent","Floor Alias","Unit Alias","Unit Balcony","Tower Type","Building Age","Segment","Car Park","Is Coop","SPV","Pet Friendly","View Type","Property Type","123"] 28 | 29 | var modelDataSource: [TagsPropertyModel] = [TagsPropertyModel]() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | view.backgroundColor = .white 34 | view.addSubview(tagsView) 35 | tagsView.backgroundColor = .orange 36 | tagsView.tagsViewDelegate = self 37 | tagsView.tagsViewContentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 38 | tagsView.minimumLineSpacing = 10; 39 | tagsView.minimumInteritemSpacing = 10; 40 | tagsView.snp.makeConstraints { (make) in 41 | make.top.equalToSuperview().offset(80) 42 | make.left.right.equalToSuperview().offset(0) 43 | } 44 | 45 | for index in 0...30 { 46 | 47 | let item = dataSource[Int(arc4random()) % dataSource.count] 48 | let model = TagsPropertyModel() 49 | model.imageAlignmentMode = .right 50 | model.titleLabel.text = item 51 | if index == 0 || index == 20 { model.titleLabel.font = .systemFont(ofSize: 20) } 52 | if index == 30 { model.titleLabel.font = .systemFont(ofSize: 40) } 53 | modelDataSource.append(model) 54 | 55 | } 56 | 57 | switch type { 58 | case .tagsViewFrameLayout: 59 | setupTagsViewFrameLayout() 60 | case .tagsViewChangeScrollDirection: 61 | setupTagsViewChangeScrollDirection() 62 | case .tagsViewShowLine: 63 | setupTagsViewShowLine() 64 | case .tagsViewManySelect: 65 | setupTagsViewManySelect() 66 | } 67 | 68 | 69 | } 70 | 71 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 72 | print("adasdasdas") 73 | } 74 | } 75 | 76 | // tagsViewFrameLayout 77 | extension InViewDemoVC { 78 | // 79 | func setupTagsViewFrameLayout() { 80 | 81 | tagsView.dataSource = dataSource 82 | tagsView.tagsViewMaxHeight = k_screenH - 20 - 40 - 80 - 80 83 | tagsView.reloadData() 84 | 85 | let button = UIButton(type: .custom) 86 | button.setTitle("添加", for: .normal) 87 | button.setTitleColor(.black, for: .normal) 88 | button.backgroundColor = .orange 89 | button.addTarget(self, action: #selector(addTagAction), for: .touchUpInside) 90 | view.addSubview(button) 91 | button.snp.makeConstraints { (make) in 92 | make.top.equalTo(tagsView.snp.bottom).offset(20) 93 | make.left.right.equalToSuperview() 94 | make.height.equalTo(40) 95 | } 96 | } 97 | 98 | @objc func addTagAction() -> () { 99 | let item = dataSource[Int(arc4random()) % dataSource.count] 100 | dataSource.append(item) 101 | tagsView.dataSource.append(item) 102 | tagsView.reloadData() 103 | } 104 | } 105 | // tagsViewChangeScrollDirection 106 | extension InViewDemoVC { 107 | func setupTagsViewChangeScrollDirection() { 108 | tagsView.isUserInteractionEnabled = false; 109 | tagsView.modelDataSource = modelDataSource 110 | tagsView.tagsViewMinHeight = 40 111 | tagsView.scrollDirection = .vertical 112 | tagsView.tagsViewMaxHeight = 400 113 | tagsView.reloadData() 114 | 115 | 116 | let button = UIButton(type: .custom) 117 | button.setTitle("更改滚动方向", for: .normal) 118 | button.setTitleColor(.black, for: .normal) 119 | button.backgroundColor = .orange 120 | button.addTarget(self, action: #selector(changeScrollDirectionTagAction(button:)), for: .touchUpInside) 121 | view.addSubview(button) 122 | button.snp.makeConstraints { (make) in 123 | make.top.equalTo(tagsView.snp.bottom).offset(20) 124 | make.left.right.equalToSuperview() 125 | make.height.equalTo(40) 126 | } 127 | } 128 | 129 | @objc func changeScrollDirectionTagAction(button: UIButton) { 130 | button.isSelected = !button.isSelected 131 | if button.isSelected { 132 | tagsView.scrollDirection = .horizontal 133 | }else { 134 | tagsView.scrollDirection = .vertical 135 | } 136 | tagsView.reloadData() 137 | } 138 | } 139 | 140 | // tagsViewShowLine 141 | extension InViewDemoVC { 142 | 143 | func setupTagsViewShowLine() { 144 | 145 | tagsView.dataSource = dataSource 146 | tagsView.showLine = 2 147 | tagsView.reloadData() 148 | } 149 | } 150 | 151 | // tagsViewManySelect 152 | extension InViewDemoVC { 153 | 154 | func setupTagsViewManySelect() { 155 | tagsView.dataSource = dataSource 156 | // 最后reload 157 | tagsView.reloadData() 158 | 159 | manySelectedResultTagsView.backgroundColor = .orange 160 | manySelectedResultTagsView.tagsViewDelegate = self 161 | manySelectedResultTagsView.tagsViewContentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 162 | manySelectedResultTagsView.minimumLineSpacing = 10; 163 | manySelectedResultTagsView.minimumInteritemSpacing = 10; 164 | view.addSubview(manySelectedResultTagsView) 165 | manySelectedResultTagsView.snp.makeConstraints { (make) in 166 | make.top.equalTo(tagsView.snp.bottom).offset(10) 167 | make.right.left.equalToSuperview().offset(0) 168 | } 169 | } 170 | } 171 | 172 | 173 | extension InViewDemoVC: LJTagsViewProtocol { 174 | 175 | /** 设置每个tag的属性,包含UI ,对应的属性*/ 176 | func tagsViewUpdatePropertyModel(_ tagsView: LJTagsView, item: TagsPropertyModel, index: NSInteger) { 177 | 178 | if (index == 15) {item.minHeight = 40} 179 | item.imageAlignmentMode = .left 180 | switch type { 181 | case .tagsViewFrameLayout: 182 | item.normalImage = UIImage(named: "delete") 183 | item.imageSize = CGSize(width: 10, height: 10) 184 | case .tagsViewChangeScrollDirection: 185 | break 186 | case .tagsViewShowLine: 187 | break 188 | case .tagsViewManySelect where tagsView == self.tagsView: 189 | item.contentView.backgroundColor = UIColor.darkGray 190 | item.contentInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10) 191 | item.normalImage = UIImage(named: "select_not") 192 | item.selectIedImage = UIImage(named: "selected") 193 | break 194 | default: 195 | break 196 | } 197 | } 198 | 199 | func tagsViewItemTapAction(_ tagsView: LJTagsView, item: TagsPropertyModel, index: NSInteger) { 200 | 201 | switch type { 202 | case .tagsViewFrameLayout: 203 | dataSource.remove(at: index) 204 | tagsView.dataSource.remove(at: index) 205 | case .tagsViewManySelect: 206 | if item.isSelected == true { 207 | manySelectedResultTagsView.dataSource.append(item.titleLabel.text!) 208 | }else { 209 | manySelectedResultTagsView.dataSource.removeAll{ $0 == item.titleLabel.text! } 210 | } 211 | manySelectedResultTagsView.reloadData() 212 | default: 213 | break 214 | } 215 | tagsView.reloadData() 216 | } 217 | 218 | func tagsViewTapAction(_ tagsView: LJTagsView) { 219 | tagsView.isSelect = !tagsView.isSelect 220 | tagsView.reloadData() 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /Examples/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 | -------------------------------------------------------------------------------- /Examples/ExamplesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesViewController.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/4/9. 6 | // 7 | 8 | import UIKit 9 | 10 | class ExamplesViewController: UIViewController { 11 | 12 | var tabView:UITableView! 13 | lazy var dataSource = Array<[String]>() 14 | static let cellId = "cellId" 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | tabView = UITableView(frame: view.bounds, style: .grouped) 20 | tabView.delegate = self 21 | tabView.dataSource = self 22 | tabView.register(UITableViewCell.self, forCellReuseIdentifier: Self.cellId) 23 | view.addSubview(tabView) 24 | 25 | dataSource.append(["自适应高度","固定高度并可以改变方向","多行展开","多选"]) 26 | dataSource.append(["tableView用例"]) 27 | } 28 | } 29 | 30 | extension ExamplesViewController: UITableViewDataSource { 31 | 32 | func numberOfSections(in tableView: UITableView) -> Int { 33 | dataSource.count 34 | } 35 | 36 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 | let section = dataSource[section] 38 | return section.count 39 | } 40 | 41 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 42 | let cell = tabView.dequeueReusableCell(withIdentifier: Self.cellId, for: indexPath) 43 | cell.textLabel?.textAlignment = .center 44 | cell.textLabel?.font = .systemFont(ofSize: 18) 45 | cell.textLabel?.text = dataSource[indexPath.section][indexPath.row] 46 | return cell 47 | } 48 | 49 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 50 | switch section { 51 | case 0: 52 | return "In-View" 53 | case 1: 54 | return "In-TableView" 55 | default: 56 | return "" 57 | } 58 | } 59 | } 60 | 61 | extension ExamplesViewController: UITableViewDelegate { 62 | 63 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 64 | 65 | switch indexPath.section { 66 | case 0: 67 | jumpInViewDemoVC(row: indexPath.row) 68 | case 1: 69 | jumpTableView() 70 | break 71 | default: 72 | break 73 | } 74 | 75 | } 76 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 77 | 60 78 | } 79 | 80 | func jumpInViewDemoVC(row: Int) { 81 | let inViewDemoVC = InViewDemoVC() 82 | if row == 0 { inViewDemoVC.type = .tagsViewFrameLayout } 83 | if row == 1 { inViewDemoVC.type = .tagsViewChangeScrollDirection } 84 | if row == 2 { inViewDemoVC.type = .tagsViewShowLine } 85 | if row == 3 { inViewDemoVC.type = .tagsViewManySelect } 86 | 87 | present(inViewDemoVC, animated: true, completion: nil) 88 | } 89 | 90 | func jumpTableView() { 91 | let vc = InTabViewVC() 92 | present(vc, animated: true, completion: nil) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Examples/LJTagsView-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Clemmie Lau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LJTagsView.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint LJTagsView.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |spec| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | spec.name = "LJTagsView" 19 | spec.version = "1.0.8" 20 | spec.summary = "轻量级的标签库,使用简单" 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | spec.description = <<-DESC 28 | 优势 :配置简单,适配auto layout,frame 布局 29 | 1.自适应高度。 30 | 2.自定义滚动方向 31 | 3.支持最大高度,最小高度 32 | 3.支持多选 33 | 4.支持多行展开 34 | 5.场景:view,cell 35 | DESC 36 | 37 | spec.homepage = "https://github.com/Clemmie-L/LJTagsView" 38 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 39 | 40 | 41 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 42 | # 43 | # Licensing your code is important. See https://choosealicense.com for more info. 44 | # CocoaPods will detect a license file if there is a named LICENSE* 45 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 46 | # 47 | 48 | spec.license = { :type => "MIT", :file => "LICENSE" } 49 | 50 | 51 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 52 | # 53 | # Specify the authors of the library, with email addresses. Email addresses 54 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 55 | # accepts just a name if you'd rather not provide an email address. 56 | # 57 | # Specify a social_media_url where others can refer to, for example a twitter 58 | # profile URL. 59 | # 60 | 61 | spec.author = { "Clemmie" => "379644692@qq.com" } 62 | # Or just: spec.author = "Clemmie" 63 | 64 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 65 | # 66 | # If this Pod runs only on iOS or OS X, then specify the platform and 67 | # the deployment target. You can optionally include the target after the platform. 68 | # 69 | 70 | 71 | # optional,如果code中用到swift,必须指定swift版本,如果用的是OC可以不用写. 72 | spec.swift_version = '5.0' 73 | 74 | # spec.platform = :ios 75 | # spec.platform = :ios, "5.0" 76 | 77 | # When using multiple platforms 78 | spec.ios.deployment_target = "14.0" 79 | # spec.osx.deployment_target = "10.7" 80 | # spec.watchos.deployment_target = "2.0" 81 | # spec.tvos.deployment_target = "9.0" 82 | 83 | 84 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 85 | # 86 | # Specify the location from where the source should be retrieved. 87 | # Supports git, hg, bzr, svn and HTTP. 88 | # 89 | 90 | spec.source = { :git => "https://github.com/Clemmie-L/LJTagsView.git", :tag => "#{spec.version}" } 91 | 92 | 93 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 94 | # 95 | # CocoaPods is smart about how it includes source code. For source files 96 | # giving a folder will include any swift, h, m, mm, c & cpp files. 97 | # For header files it will include any header in the folder. 98 | # Not including the public_header_files will make all headers public. 99 | # 100 | 101 | spec.source_files = "LJTagsView/LJTagsView/*.swift" 102 | spec.resources = "LJTagsView/LJTagsView/*.bundle" 103 | 104 | # spec.exclude_files = "Classes/Exclude" 105 | # spec.public_header_files = "Classes/**/*.h" 106 | 107 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 108 | # 109 | # A list of resources included with the Pod. These are copied into the 110 | # target bundle with a build phase script. Anything else will be cleaned. 111 | # You can preserve files from being cleaned, please don't preserve 112 | # non-essential files like tests, examples and documentation. 113 | # 114 | 115 | # spec.resource = "icon.png" 116 | # spec.resources = "Resources/*.png" 117 | 118 | # spec.preserve_paths = "FilesToSave", "MoreFilesToSave" 119 | 120 | 121 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 122 | # 123 | # Link your library with frameworks, or libraries. Libraries do not include 124 | # the lib prefix of their name. 125 | # 126 | 127 | # spec.framework = "SomeFramework" 128 | # spec.frameworks = "SomeFramework", "AnotherFramework" 129 | 130 | # spec.library = "iconv" 131 | # spec.libraries = "iconv", "xml2" 132 | 133 | 134 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 135 | # 136 | # If your library depends on compiler flags you can set them in the xcconfig hash 137 | # where they will only apply to your library. If you depend on other Podspecs 138 | # you can include multiple dependencies to ensure it works. 139 | 140 | # spec.requires_arc = true 141 | 142 | # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 143 | # spec.dependency "JSONKit", "~> 1.4" 144 | 145 | end 146 | -------------------------------------------------------------------------------- /LJTagsView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0E5DD2B8261FF0E000CA4B83 /* InViewDemoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5DD2B7261FF0E000CA4B83 /* InViewDemoVC.swift */; }; 11 | 0E5DD2BC261FF10900CA4B83 /* InTabViewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5DD2BB261FF10900CA4B83 /* InTabViewVC.swift */; }; 12 | 0E5DD2C2261FFB2800CA4B83 /* ExamplesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5DD2C1261FFB2800CA4B83 /* ExamplesViewController.swift */; }; 13 | 0E987AFD25F8A29600C33005 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E987AFC25F8A29600C33005 /* AppDelegate.swift */; }; 14 | 0E987AFF25F8A29600C33005 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E987AFE25F8A29600C33005 /* SceneDelegate.swift */; }; 15 | 0E987B0425F8A29600C33005 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E987B0225F8A29600C33005 /* Main.storyboard */; }; 16 | 0E987B0625F8A29C00C33005 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E987B0525F8A29C00C33005 /* Assets.xcassets */; }; 17 | 0E987B0925F8A29C00C33005 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E987B0725F8A29C00C33005 /* LaunchScreen.storyboard */; }; 18 | 0E987B1725F8A3C100C33005 /* LJTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E987B1625F8A3C100C33005 /* LJTagsView.swift */; }; 19 | 0E987B1F25F8A44700C33005 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E987B1C25F8A44700C33005 /* TableViewCell.swift */; }; 20 | 419C5E42C0C2762A0960F510 /* Pods_LJTagsView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4C55C498014608CE36BC2A0 /* Pods_LJTagsView.framework */; }; 21 | A46EE38E2BB2775F006A4D66 /* LJTagsView.bundle in Resources */ = {isa = PBXBuildFile; fileRef = A46EE38D2BB2775F006A4D66 /* LJTagsView.bundle */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 0E5DD2B7261FF0E000CA4B83 /* InViewDemoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InViewDemoVC.swift; sourceTree = ""; }; 26 | 0E5DD2BB261FF10900CA4B83 /* InTabViewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InTabViewVC.swift; sourceTree = ""; }; 27 | 0E5DD2C1261FFB2800CA4B83 /* ExamplesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesViewController.swift; sourceTree = ""; }; 28 | 0E5DD2C7261FFF5100CA4B83 /* LJTagsView-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LJTagsView-Bridging-Header.h"; sourceTree = ""; }; 29 | 0E987AF925F8A29600C33005 /* LJTagsView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LJTagsView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 0E987AFC25F8A29600C33005 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | 0E987AFE25F8A29600C33005 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 32 | 0E987B0325F8A29600C33005 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | 0E987B0525F8A29C00C33005 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | 0E987B0825F8A29C00C33005 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35 | 0E987B0A25F8A29C00C33005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 0E987B1625F8A3C100C33005 /* LJTagsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LJTagsView.swift; sourceTree = ""; }; 37 | 0E987B1C25F8A44700C33005 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; 38 | 2CF8FC1A04DFE6F0E26B0BB8 /* Pods-LJTagsView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LJTagsView.release.xcconfig"; path = "Target Support Files/Pods-LJTagsView/Pods-LJTagsView.release.xcconfig"; sourceTree = ""; }; 39 | A46EE38D2BB2775F006A4D66 /* LJTagsView.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = LJTagsView.bundle; sourceTree = ""; }; 40 | E51ADFF4041FB2F72EC48D35 /* Pods-LJTagsView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LJTagsView.debug.xcconfig"; path = "Target Support Files/Pods-LJTagsView/Pods-LJTagsView.debug.xcconfig"; sourceTree = ""; }; 41 | F4C55C498014608CE36BC2A0 /* Pods_LJTagsView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LJTagsView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 0E987AF625F8A29600C33005 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 419C5E42C0C2762A0960F510 /* Pods_LJTagsView.framework in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 0E5DD2B5261FF09400CA4B83 /* Demo */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 0E5DD2BA261FF0EA00CA4B83 /* InTabViewDemo */, 60 | 0E5DD2B6261FF0B000CA4B83 /* InViewDemo */, 61 | ); 62 | path = Demo; 63 | sourceTree = ""; 64 | }; 65 | 0E5DD2B6261FF0B000CA4B83 /* InViewDemo */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 0E5DD2B7261FF0E000CA4B83 /* InViewDemoVC.swift */, 69 | ); 70 | path = InViewDemo; 71 | sourceTree = ""; 72 | }; 73 | 0E5DD2BA261FF0EA00CA4B83 /* InTabViewDemo */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 0E5DD2BB261FF10900CA4B83 /* InTabViewVC.swift */, 77 | 0E987B1C25F8A44700C33005 /* TableViewCell.swift */, 78 | ); 79 | path = InTabViewDemo; 80 | sourceTree = ""; 81 | }; 82 | 0E5DD2C0261FFAF600CA4B83 /* Examples */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 0E987B0225F8A29600C33005 /* Main.storyboard */, 86 | 0E5DD2C1261FFB2800CA4B83 /* ExamplesViewController.swift */, 87 | 0E5DD2C7261FFF5100CA4B83 /* LJTagsView-Bridging-Header.h */, 88 | ); 89 | path = Examples; 90 | sourceTree = ""; 91 | }; 92 | 0E987AF025F8A29600C33005 = { 93 | isa = PBXGroup; 94 | children = ( 95 | 0E5DD2C0261FFAF600CA4B83 /* Examples */, 96 | 0E5DD2B5261FF09400CA4B83 /* Demo */, 97 | 0E987AFB25F8A29600C33005 /* LJTagsView */, 98 | 0E987AFA25F8A29600C33005 /* Products */, 99 | 3E758D31E5796C7C93A95097 /* Pods */, 100 | FDFE9C613E6E61AC0F701C97 /* Frameworks */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 0E987AFA25F8A29600C33005 /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 0E987AF925F8A29600C33005 /* LJTagsView.app */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 0E987AFB25F8A29600C33005 /* LJTagsView */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 0E987B1525F8A32300C33005 /* LJTagsView */, 116 | 0E987AFC25F8A29600C33005 /* AppDelegate.swift */, 117 | 0E987AFE25F8A29600C33005 /* SceneDelegate.swift */, 118 | 0E987B0525F8A29C00C33005 /* Assets.xcassets */, 119 | 0E987B0725F8A29C00C33005 /* LaunchScreen.storyboard */, 120 | 0E987B0A25F8A29C00C33005 /* Info.plist */, 121 | ); 122 | path = LJTagsView; 123 | sourceTree = ""; 124 | }; 125 | 0E987B1525F8A32300C33005 /* LJTagsView */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 0E987B1625F8A3C100C33005 /* LJTagsView.swift */, 129 | A46EE38D2BB2775F006A4D66 /* LJTagsView.bundle */, 130 | ); 131 | path = LJTagsView; 132 | sourceTree = ""; 133 | }; 134 | 3E758D31E5796C7C93A95097 /* Pods */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | E51ADFF4041FB2F72EC48D35 /* Pods-LJTagsView.debug.xcconfig */, 138 | 2CF8FC1A04DFE6F0E26B0BB8 /* Pods-LJTagsView.release.xcconfig */, 139 | ); 140 | path = Pods; 141 | sourceTree = ""; 142 | }; 143 | FDFE9C613E6E61AC0F701C97 /* Frameworks */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | F4C55C498014608CE36BC2A0 /* Pods_LJTagsView.framework */, 147 | ); 148 | name = Frameworks; 149 | sourceTree = ""; 150 | }; 151 | /* End PBXGroup section */ 152 | 153 | /* Begin PBXNativeTarget section */ 154 | 0E987AF825F8A29600C33005 /* LJTagsView */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = 0E987B0D25F8A29C00C33005 /* Build configuration list for PBXNativeTarget "LJTagsView" */; 157 | buildPhases = ( 158 | 16AFCFB67986E08A24AC9E32 /* [CP] Check Pods Manifest.lock */, 159 | 0E987AF525F8A29600C33005 /* Sources */, 160 | 0E987AF625F8A29600C33005 /* Frameworks */, 161 | 0E987AF725F8A29600C33005 /* Resources */, 162 | CAB087F7E1B4FF1DB396D8CE /* [CP] Embed Pods Frameworks */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = LJTagsView; 169 | productName = LJTagsView; 170 | productReference = 0E987AF925F8A29600C33005 /* LJTagsView.app */; 171 | productType = "com.apple.product-type.application"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | 0E987AF125F8A29600C33005 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastSwiftUpdateCheck = 1240; 180 | LastUpgradeCheck = 1240; 181 | TargetAttributes = { 182 | 0E987AF825F8A29600C33005 = { 183 | CreatedOnToolsVersion = 12.4; 184 | LastSwiftMigration = 1240; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = 0E987AF425F8A29600C33005 /* Build configuration list for PBXProject "LJTagsView" */; 189 | compatibilityVersion = "Xcode 9.3"; 190 | developmentRegion = en; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = 0E987AF025F8A29600C33005; 197 | productRefGroup = 0E987AFA25F8A29600C33005 /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 0E987AF825F8A29600C33005 /* LJTagsView */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | 0E987AF725F8A29600C33005 /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 0E987B0925F8A29C00C33005 /* LaunchScreen.storyboard in Resources */, 212 | A46EE38E2BB2775F006A4D66 /* LJTagsView.bundle in Resources */, 213 | 0E987B0625F8A29C00C33005 /* Assets.xcassets in Resources */, 214 | 0E987B0425F8A29600C33005 /* Main.storyboard in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXShellScriptBuildPhase section */ 221 | 16AFCFB67986E08A24AC9E32 /* [CP] Check Pods Manifest.lock */ = { 222 | isa = PBXShellScriptBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | inputFileListPaths = ( 227 | ); 228 | inputPaths = ( 229 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 230 | "${PODS_ROOT}/Manifest.lock", 231 | ); 232 | name = "[CP] Check Pods Manifest.lock"; 233 | outputFileListPaths = ( 234 | ); 235 | outputPaths = ( 236 | "$(DERIVED_FILE_DIR)/Pods-LJTagsView-checkManifestLockResult.txt", 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | shellPath = /bin/sh; 240 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 241 | showEnvVarsInLog = 0; 242 | }; 243 | CAB087F7E1B4FF1DB396D8CE /* [CP] Embed Pods Frameworks */ = { 244 | isa = PBXShellScriptBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | ); 248 | inputFileListPaths = ( 249 | "${PODS_ROOT}/Target Support Files/Pods-LJTagsView/Pods-LJTagsView-frameworks-${CONFIGURATION}-input-files.xcfilelist", 250 | ); 251 | name = "[CP] Embed Pods Frameworks"; 252 | outputFileListPaths = ( 253 | "${PODS_ROOT}/Target Support Files/Pods-LJTagsView/Pods-LJTagsView-frameworks-${CONFIGURATION}-output-files.xcfilelist", 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | shellPath = /bin/sh; 257 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LJTagsView/Pods-LJTagsView-frameworks.sh\"\n"; 258 | showEnvVarsInLog = 0; 259 | }; 260 | /* End PBXShellScriptBuildPhase section */ 261 | 262 | /* Begin PBXSourcesBuildPhase section */ 263 | 0E987AF525F8A29600C33005 /* Sources */ = { 264 | isa = PBXSourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 0E987AFD25F8A29600C33005 /* AppDelegate.swift in Sources */, 268 | 0E5DD2B8261FF0E000CA4B83 /* InViewDemoVC.swift in Sources */, 269 | 0E987AFF25F8A29600C33005 /* SceneDelegate.swift in Sources */, 270 | 0E5DD2C2261FFB2800CA4B83 /* ExamplesViewController.swift in Sources */, 271 | 0E987B1F25F8A44700C33005 /* TableViewCell.swift in Sources */, 272 | 0E5DD2BC261FF10900CA4B83 /* InTabViewVC.swift in Sources */, 273 | 0E987B1725F8A3C100C33005 /* LJTagsView.swift in Sources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXSourcesBuildPhase section */ 278 | 279 | /* Begin PBXVariantGroup section */ 280 | 0E987B0225F8A29600C33005 /* Main.storyboard */ = { 281 | isa = PBXVariantGroup; 282 | children = ( 283 | 0E987B0325F8A29600C33005 /* Base */, 284 | ); 285 | name = Main.storyboard; 286 | sourceTree = ""; 287 | }; 288 | 0E987B0725F8A29C00C33005 /* LaunchScreen.storyboard */ = { 289 | isa = PBXVariantGroup; 290 | children = ( 291 | 0E987B0825F8A29C00C33005 /* Base */, 292 | ); 293 | name = LaunchScreen.storyboard; 294 | sourceTree = ""; 295 | }; 296 | /* End PBXVariantGroup section */ 297 | 298 | /* Begin XCBuildConfiguration section */ 299 | 0E987B0B25F8A29C00C33005 /* Debug */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ALWAYS_SEARCH_USER_PATHS = NO; 303 | CLANG_ANALYZER_NONNULL = YES; 304 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 305 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 306 | CLANG_CXX_LIBRARY = "libc++"; 307 | CLANG_ENABLE_MODULES = YES; 308 | CLANG_ENABLE_OBJC_ARC = YES; 309 | CLANG_ENABLE_OBJC_WEAK = YES; 310 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 311 | CLANG_WARN_BOOL_CONVERSION = YES; 312 | CLANG_WARN_COMMA = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 326 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 327 | CLANG_WARN_STRICT_PROTOTYPES = YES; 328 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 329 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 330 | CLANG_WARN_UNREACHABLE_CODE = YES; 331 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 332 | COPY_PHASE_STRIP = NO; 333 | DEBUG_INFORMATION_FORMAT = dwarf; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | ENABLE_TESTABILITY = YES; 336 | GCC_C_LANGUAGE_STANDARD = gnu11; 337 | GCC_DYNAMIC_NO_PIC = NO; 338 | GCC_NO_COMMON_BLOCKS = YES; 339 | GCC_OPTIMIZATION_LEVEL = 0; 340 | GCC_PREPROCESSOR_DEFINITIONS = ( 341 | "DEBUG=1", 342 | "$(inherited)", 343 | ); 344 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 345 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 346 | GCC_WARN_UNDECLARED_SELECTOR = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 348 | GCC_WARN_UNUSED_FUNCTION = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 351 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 352 | MTL_FAST_MATH = YES; 353 | ONLY_ACTIVE_ARCH = YES; 354 | SDKROOT = iphoneos; 355 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 356 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 357 | }; 358 | name = Debug; 359 | }; 360 | 0E987B0C25F8A29C00C33005 /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_ANALYZER_NONNULL = YES; 365 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 366 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 367 | CLANG_CXX_LIBRARY = "libc++"; 368 | CLANG_ENABLE_MODULES = YES; 369 | CLANG_ENABLE_OBJC_ARC = YES; 370 | CLANG_ENABLE_OBJC_WEAK = YES; 371 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 372 | CLANG_WARN_BOOL_CONVERSION = YES; 373 | CLANG_WARN_COMMA = YES; 374 | CLANG_WARN_CONSTANT_CONVERSION = YES; 375 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 378 | CLANG_WARN_EMPTY_BODY = YES; 379 | CLANG_WARN_ENUM_CONVERSION = YES; 380 | CLANG_WARN_INFINITE_RECURSION = YES; 381 | CLANG_WARN_INT_CONVERSION = YES; 382 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 383 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 384 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 388 | CLANG_WARN_STRICT_PROTOTYPES = YES; 389 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 390 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | COPY_PHASE_STRIP = NO; 394 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 395 | ENABLE_NS_ASSERTIONS = NO; 396 | ENABLE_STRICT_OBJC_MSGSEND = YES; 397 | GCC_C_LANGUAGE_STANDARD = gnu11; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 401 | GCC_WARN_UNDECLARED_SELECTOR = YES; 402 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 403 | GCC_WARN_UNUSED_FUNCTION = YES; 404 | GCC_WARN_UNUSED_VARIABLE = YES; 405 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 406 | MTL_ENABLE_DEBUG_INFO = NO; 407 | MTL_FAST_MATH = YES; 408 | SDKROOT = iphoneos; 409 | SWIFT_COMPILATION_MODE = wholemodule; 410 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 411 | VALIDATE_PRODUCT = YES; 412 | }; 413 | name = Release; 414 | }; 415 | 0E987B0E25F8A29C00C33005 /* Debug */ = { 416 | isa = XCBuildConfiguration; 417 | baseConfigurationReference = E51ADFF4041FB2F72EC48D35 /* Pods-LJTagsView.debug.xcconfig */; 418 | buildSettings = { 419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 420 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 421 | CLANG_ENABLE_MODULES = YES; 422 | CODE_SIGN_STYLE = Automatic; 423 | DEVELOPMENT_TEAM = 364S6M3263; 424 | INFOPLIST_FILE = LJTagsView/Info.plist; 425 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | ); 430 | MARKETING_VERSION = 1.0.1; 431 | PRODUCT_BUNDLE_IDENTIFIER = com.imanagesystems.LJTagsView; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | SWIFT_OBJC_BRIDGING_HEADER = "Examples/LJTagsView-Bridging-Header.h"; 434 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 435 | SWIFT_VERSION = 5.0; 436 | TARGETED_DEVICE_FAMILY = "1,2"; 437 | }; 438 | name = Debug; 439 | }; 440 | 0E987B0F25F8A29C00C33005 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | baseConfigurationReference = 2CF8FC1A04DFE6F0E26B0BB8 /* Pods-LJTagsView.release.xcconfig */; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 446 | CLANG_ENABLE_MODULES = YES; 447 | CODE_SIGN_STYLE = Automatic; 448 | DEVELOPMENT_TEAM = 364S6M3263; 449 | INFOPLIST_FILE = LJTagsView/Info.plist; 450 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "@executable_path/Frameworks", 454 | ); 455 | MARKETING_VERSION = 1.0.1; 456 | PRODUCT_BUNDLE_IDENTIFIER = com.imanagesystems.LJTagsView; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | SWIFT_OBJC_BRIDGING_HEADER = "Examples/LJTagsView-Bridging-Header.h"; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Release; 463 | }; 464 | /* End XCBuildConfiguration section */ 465 | 466 | /* Begin XCConfigurationList section */ 467 | 0E987AF425F8A29600C33005 /* Build configuration list for PBXProject "LJTagsView" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 0E987B0B25F8A29C00C33005 /* Debug */, 471 | 0E987B0C25F8A29C00C33005 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | 0E987B0D25F8A29C00C33005 /* Build configuration list for PBXNativeTarget "LJTagsView" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 0E987B0E25F8A29C00C33005 /* Debug */, 480 | 0E987B0F25F8A29C00C33005 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | /* End XCConfigurationList section */ 486 | }; 487 | rootObject = 0E987AF125F8A29600C33005 /* Project object */; 488 | } 489 | -------------------------------------------------------------------------------- /LJTagsView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LJTagsView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LJTagsView.xcodeproj/project.xcworkspace/xcuserdata/ims_mac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/LJTagsView.xcodeproj/project.xcworkspace/xcuserdata/ims_mac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /LJTagsView.xcodeproj/xcuserdata/ims_mac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | LJTagsView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LJTagsView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/3/10. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/select_not.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "select_not@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "select_not@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/select_not.imageset/select_not@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/LJTagsView/Assets.xcassets/select_not.imageset/select_not@2x.png -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/select_not.imageset/select_not@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/LJTagsView/Assets.xcassets/select_not.imageset/select_not@3x.png -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "selected@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "selected@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/selected.imageset/selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/LJTagsView/Assets.xcassets/selected.imageset/selected@2x.png -------------------------------------------------------------------------------- /LJTagsView/Assets.xcassets/selected.imageset/selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/LJTagsView/Assets.xcassets/selected.imageset/selected@3x.png -------------------------------------------------------------------------------- /LJTagsView/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /LJTagsView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /LJTagsView/LJTagsView/LJTagsView.bundle/arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/LJTagsView/LJTagsView/LJTagsView.bundle/arrow@2x.png -------------------------------------------------------------------------------- /LJTagsView/LJTagsView/LJTagsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LJTagsView.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/3/3. 6 | // 7 | 8 | import UIKit 9 | 10 | /// LJTagsViewProtocol 11 | @objc public protocol LJTagsViewProtocol: NSObjectProtocol { 12 | 13 | @objc optional func tagsViewUpdatePropertyModel(_ tagsView: LJTagsView, item: TagsPropertyModel,index: NSInteger) -> Void 14 | @objc optional func tagsViewUpdateHeight(_ tagsView: LJTagsView, sumHeight: CGFloat) -> Void 15 | @objc optional func tagsViewTapAction(_ tagsView: LJTagsView) -> Void 16 | @objc optional func tagsViewItemTapAction(_ tagsView: LJTagsView, item: TagsPropertyModel,index: NSInteger) -> Void 17 | 18 | } 19 | 20 | @objc public enum tagsViewScrollDirection: Int { 21 | case vertical = 0 // 垂直方向 22 | case horizontal = 1 // 水平方向 23 | } 24 | 25 | public class LJTagsView: UIView { 26 | 27 | /** 数据源*/ 28 | @objc public var dataSource: [String] = [] { 29 | didSet { 30 | config() 31 | } 32 | } 33 | 34 | /** 数据源*/ 35 | @objc public var modelDataSource: [TagsPropertyModel] = [] { 36 | didSet { 37 | config() 38 | } 39 | } 40 | 41 | /** 标签行间距 default is 10*/ 42 | @objc public var minimumLineSpacing: CGFloat = 10.0 43 | 44 | /** 标签的间距 default is 10*/ 45 | @objc public var minimumInteritemSpacing: CGFloat = 10.0 46 | 47 | /** tagsSupView的边距 default is top:0,letf:0,bottom:0,right:0*/ 48 | @objc public var tagsViewContentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 49 | 50 | /** tagsView 最小高度 default is 0.0 */ 51 | @objc public var tagsViewMinHeight: CGFloat = 0.0 { 52 | didSet { 53 | // 限制条件 54 | tagsViewMinHeight = tagsViewMinHeight > tagsViewMaxHeight ? tagsViewMaxHeight : tagsViewMinHeight 55 | } 56 | } 57 | 58 | /** tagsView 最大高度 default is MAXFLOAT;当contentSize 大于tagsViewMaxHeight ,则会滚动;scrollDirection == horizontal时,这个属性失效*/ 59 | @objc public var tagsViewMaxHeight: CGFloat = CGFloat(MAXFLOAT) { 60 | didSet { 61 | // 限制条件 62 | tagsViewMaxHeight = tagsViewMaxHeight < tagsViewMinHeight ? tagsViewMinHeight : tagsViewMaxHeight 63 | } 64 | } 65 | 66 | /** tagsView 滚动方向 default is Vertical*/ 67 | @objc public var scrollDirection : tagsViewScrollDirection = .vertical 68 | 69 | /** 代理*/ 70 | @objc open weak var tagsViewDelegate : LJTagsViewProtocol? 71 | 72 | /** 默认显示行数,0 为全部显示, 设置showLine: tagsViewScrollDirection = horizontal 无效*/ 73 | @objc public var showLine: UInt = 0 { 74 | willSet { 75 | if newValue > 0 { 76 | let tap = UITapGestureRecognizer.init(target: self, action: #selector(tagsViewTapAction)) 77 | self.addGestureRecognizer(tap) 78 | } 79 | } 80 | } 81 | 82 | /** showLine 大于0 的时候 显示*/ 83 | @objc public var arrowImageView: UIImageView = UIImageView.init(image: UIImage.imageFormBundle(with: "arrow")?.withRenderingMode(.alwaysOriginal)) 84 | 85 | /** 是否选中*/ 86 | @objc public var isSelect = false 87 | 88 | /** tagsView 宽度 default is 屏幕宽度 */ 89 | private var tagsViewWidth = UIScreen.main.bounds.width 90 | 91 | /** 记录*/ 92 | private var dealDataSource: [TagsPropertyModel] = [TagsPropertyModel]() 93 | 94 | private var scrollView = UIScrollView() 95 | 96 | private var contentSize = CGSize.zero 97 | 98 | public override init(frame:CGRect) { 99 | super.init(frame: frame) 100 | scrollView.showsVerticalScrollIndicator = false 101 | scrollView.showsHorizontalScrollIndicator = false 102 | arrowImageView.isHidden = true 103 | addSubview(scrollView) 104 | addSubview(arrowImageView) 105 | } 106 | 107 | required init?(coder: NSCoder) { 108 | fatalError("init(coder:) has not been implemented") 109 | } 110 | } 111 | 112 | //MARK: -- setup数据 113 | extension LJTagsView { 114 | 115 | @objc public func reloadData() -> Void { 116 | 117 | layoutIfNeeded() 118 | 119 | var tagX: CGFloat = tagsViewContentInset.left 120 | var tagY: CGFloat = tagsViewContentInset.top 121 | var tagW: CGFloat = 0.0 122 | var tagH: CGFloat = 0.0 123 | 124 | var labelW: CGFloat = 0.0 125 | var labelH: CGFloat = 0.0 126 | var LableY: CGFloat = 0.0 127 | var imageY: CGFloat = 0.0 128 | 129 | // 下一个tag的宽度 130 | var nextTagW: CGFloat = 0.0 131 | // 记录当前的行数, 132 | var currentLine: UInt = 1 133 | // 记录当前行数的全部数据 134 | var showLineDataSource: [TagsPropertyModel] = [TagsPropertyModel]() 135 | 136 | // 设置arroImageView 137 | arrowImageView.isHidden = !(dealDataSource.count > 0 && showLine > 0) 138 | if arrowImageView.isHidden { 139 | tagsViewWidth = frame.width 140 | }else { 141 | let arrowImageViewSize = arrowImageView.bounds.size 142 | arrowImageView.frame = CGRect(x: frame.width - tagsViewContentInset.right - arrowImageViewSize.width, y: tagsViewContentInset.top, width: arrowImageViewSize.width, height: arrowImageViewSize.height) 143 | tagsViewWidth = frame.width - arrowImageViewSize.width - 10 144 | let angle = isSelect == true ? Double.pi : 0.0 145 | // UIView.animate(withDuration: 0.3) { [unowned self] in 146 | arrowImageView.transform = CGAffineTransform.init(rotationAngle: CGFloat(angle)) 147 | // } 148 | } 149 | 150 | for (index,propertyModel) in dealDataSource.enumerated() { 151 | 152 | if tagsViewDelegate?.responds(to: #selector(tagsViewDelegate?.tagsViewItemTapAction(_:item:index:))) ?? false { 153 | let tap = UITapGestureRecognizer(target: self, action: #selector(contentViewTapAction(gestureRecongizer:))) 154 | propertyModel.contentView.addGestureRecognizer(tap) 155 | 156 | } 157 | 158 | propertyModel.contentView.tag = index 159 | 160 | tagW = tagWidth(propertyModel) 161 | 162 | switch scrollDirection { 163 | case .vertical: 164 | if tagW > tagsViewWidth - tagsViewContentInset.left - tagsViewContentInset.right { 165 | tagW = tagsViewWidth - tagsViewContentInset.left - tagsViewContentInset.right 166 | } 167 | case .horizontal: 168 | break 169 | } 170 | 171 | labelW = tagW - (propertyModel.contentInset.left + propertyModel.contentInset.right + propertyModel.tagContentPadding + propertyModel.imageWidth) 172 | 173 | labelH = tagHeight(propertyModel, width: labelW) 174 | 175 | let contentH = labelH < propertyModel.imageHeight ? propertyModel.imageHeight : labelH 176 | 177 | tagH = contentH + propertyModel.contentInset.top + propertyModel.contentInset.bottom 178 | 179 | tagH = tagH < propertyModel.minHeight ? propertyModel.minHeight : tagH 180 | 181 | LableY = (tagH - labelH) * 0.5 182 | 183 | imageY = (tagH - propertyModel.imageHeight) * 0.5 184 | 185 | propertyModel.contentView.frame = CGRect(x: tagX, y: tagY, width: tagW, height: tagH) 186 | 187 | switch propertyModel.imageAlignmentMode { 188 | case .left: 189 | propertyModel.imageView.frame = CGRect(x: propertyModel.contentInset.left, y: imageY, width: propertyModel.imageWidth, height: propertyModel.imageHeight) 190 | propertyModel.titleLabel.frame = CGRect(x: propertyModel.imageView.frame.maxX + propertyModel.tagContentPadding, y: LableY, width: labelW, height: labelH) 191 | case .right: 192 | propertyModel.titleLabel.frame = CGRect(x: propertyModel.contentInset.left, y: LableY, width: labelW, height: labelH) 193 | propertyModel.imageView.frame = CGRect(x: propertyModel.titleLabel.frame.maxX + propertyModel.tagContentPadding, y: imageY, width: propertyModel.imageWidth, height: propertyModel.imageHeight) 194 | } 195 | 196 | if showLine >= currentLine { 197 | showLineDataSource.append(propertyModel) 198 | } 199 | 200 | // 下一个tag,X,Y位置 201 | let nextTagX = tagX + tagW + minimumInteritemSpacing 202 | 203 | switch scrollDirection { 204 | case .vertical: 205 | // 获取下一个tag的宽度 206 | if index < dealDataSource.count - 1 { 207 | let nextIndex = index + 1 208 | let nextPropertyModel = dealDataSource[nextIndex] 209 | nextTagW = tagWidth(nextPropertyModel) 210 | } 211 | if nextTagX + nextTagW + tagsViewContentInset.right > tagsViewWidth { 212 | 213 | currentLine = currentLine + 1 214 | tagX = tagsViewContentInset.left 215 | let subDealDataSource = dealDataSource[0...index] 216 | let maxYModel = subDealDataSource.max { (m1, m2) -> Bool in 217 | return m1.contentView.frame.maxY < m2.contentView.frame.maxY 218 | } 219 | let lastObjFrame = maxYModel!.contentView.frame 220 | tagY = lastObjFrame.maxY + minimumLineSpacing 221 | }else { 222 | tagX = nextTagX 223 | } 224 | case .horizontal: 225 | tagX = nextTagX 226 | } 227 | } 228 | 229 | // 最大收合数 等于 总数量 说明 不需要展开 隐藏箭头图标 230 | if showLineDataSource.count == dealDataSource.count { 231 | arrowImageView.isHidden = true 232 | } 233 | 234 | var sumHeight = tagsViewMinHeight 235 | var scrollContentSize = CGSize.zero 236 | var viewContentSize = CGSize(width: tagsViewWidth, height: sumHeight) 237 | 238 | switch scrollDirection { 239 | case .vertical: 240 | if dealDataSource.count != 0 { 241 | let resultDataSource = showLine > 0 && isSelect == false ? showLineDataSource : 242 | dealDataSource 243 | let lastPropertyModel = filterMaxYModel(dataSource: resultDataSource, standardModel: resultDataSource.last!) 244 | sumHeight = lastPropertyModel.contentView.frame.maxY + tagsViewContentInset.bottom 245 | scrollContentSize = CGSize(width: tagsViewWidth, height: sumHeight) 246 | sumHeight = sumHeight > tagsViewMaxHeight ? tagsViewMaxHeight : sumHeight 247 | viewContentSize = CGSize(width: tagsViewWidth, height: sumHeight) 248 | } 249 | case .horizontal: 250 | if dealDataSource.count != 0 { 251 | let lastPropertyModel = filterMaxYModel(dataSource: dealDataSource, standardModel: dealDataSource.last!) 252 | let sumWidth = lastPropertyModel.contentView.frame.maxX + tagsViewContentInset.right 253 | sumHeight = lastPropertyModel.contentView.frame.maxY + tagsViewContentInset.bottom 254 | scrollContentSize = CGSize(width: sumWidth, height: sumHeight) 255 | viewContentSize = scrollContentSize 256 | } 257 | } 258 | 259 | frame.size.height = sumHeight 260 | scrollView.frame = CGRect(x: 0, y: 0, width: tagsViewWidth, height: sumHeight) 261 | scrollView.contentSize = scrollContentSize; 262 | 263 | if (!contentSize.equalTo(viewContentSize)) { 264 | contentSize = viewContentSize; 265 | // 通知外部IntrinsicContentSize失效 266 | invalidateIntrinsicContentSize() 267 | } 268 | 269 | tagsViewDelegate?.tagsViewUpdateHeight?(self, sumHeight: sumHeight) 270 | 271 | } 272 | 273 | public override var intrinsicContentSize: CGSize { 274 | return contentSize 275 | } 276 | } 277 | 278 | //MARK: -- private 279 | extension LJTagsView { 280 | 281 | private func config() { 282 | 283 | dealDataSource.removeAll() 284 | scrollView.subviews.forEach { $0.removeFromSuperview() } 285 | 286 | if dataSource.count > 0 { 287 | for (index, value) in dataSource.enumerated() { 288 | let propertyModel = TagsPropertyModel() 289 | propertyModel.titleLabel.text = value 290 | if let d = tagsViewDelegate { 291 | d.tagsViewUpdatePropertyModel?(self, item: propertyModel, index: index) 292 | } 293 | scrollView.addSubview(propertyModel.contentView) 294 | dealDataSource.append(propertyModel) 295 | } 296 | }else { 297 | for (index,propertyModel) in modelDataSource.enumerated() { 298 | if let d = tagsViewDelegate { 299 | d.tagsViewUpdatePropertyModel?(self, item: propertyModel, index: index) 300 | } 301 | scrollView.addSubview(propertyModel.contentView) 302 | dealDataSource.append(propertyModel) 303 | } 304 | } 305 | 306 | } 307 | 308 | private func tagWidth(_ model: TagsPropertyModel) -> CGFloat { 309 | let w = ceil(sizeWidthText(model).width) + 0.5 + model.contentInset.left + model.contentInset.right + model.tagContentPadding + model.imageWidth 310 | return w 311 | } 312 | 313 | private func sizeWidthText(_ model: TagsPropertyModel) -> CGSize { 314 | return model.titleLabel.text?.size(withAttributes: [.font : model.titleLabel.font!]) ?? CGSize.zero 315 | } 316 | 317 | private func tagHeight(_ model: TagsPropertyModel, width: CGFloat) -> CGFloat { 318 | 319 | // if let attributedText = model.titleLabel.attributedText { 320 | // let size = attributedText.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: [.usesLineFragmentOrigin,.usesFontLeading,.truncatesLastVisibleLine], context: nil) 321 | // return ceil(size.height) 322 | // } 323 | 324 | if let text = model.titleLabel.text { 325 | let size = text.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options:[.usesLineFragmentOrigin,.usesFontLeading,.truncatesLastVisibleLine], attributes: [.font : model.titleLabel.font!], context: nil) 326 | return ceil(size.height) 327 | } 328 | return 0.0 329 | } 330 | 331 | // filter - result return maxYModel 332 | private func filterMaxYModel(dataSource:[TagsPropertyModel],standardModel:TagsPropertyModel) -> TagsPropertyModel { 333 | let maxYModel = dataSource.filter { (m) -> Bool in 334 | m.contentView.frame.minY == standardModel.contentView.frame.minY 335 | }.max { (m1, m2) -> Bool in 336 | return m1.contentView.frame.maxY <= m2.contentView.frame.maxY 337 | } 338 | return maxYModel! 339 | } 340 | } 341 | 342 | //MARK: -- action 343 | extension LJTagsView { 344 | 345 | @objc func contentViewTapAction(gestureRecongizer: UIGestureRecognizer) { 346 | let int = gestureRecongizer.view?.tag ?? 0 347 | if let d = tagsViewDelegate { 348 | let item = dealDataSource[int]; 349 | item.isSelected = !item.isSelected 350 | d.tagsViewItemTapAction?(self, item: dealDataSource[int], index: int) 351 | } 352 | } 353 | 354 | @objc func tagsViewTapAction() { 355 | tagsViewDelegate?.tagsViewTapAction?(self) 356 | } 357 | } 358 | 359 | //MARK: -- 设置每个tag的属性 360 | public class TagsPropertyModel: NSObject { 361 | 362 | @objc public enum TagImageViewAlignmentMode: Int { 363 | case right 364 | case left 365 | } 366 | 367 | /** 正常的图片*/ 368 | @objc public var normalImage:UIImage? { 369 | willSet { 370 | if selectIedImage == nil { selectIedImage = newValue } 371 | if isSelected == false { imageView.image = newValue } 372 | } 373 | } 374 | /** 选中状态的图片*/ 375 | @objc public var selectIedImage:UIImage? { 376 | willSet { 377 | if isSelected == true { imageView.image = newValue } 378 | } 379 | } 380 | /** 图片的大小*/ 381 | @objc public var imageSize: CGSize { 382 | willSet { 383 | imageView.frame.size = newValue 384 | } 385 | } 386 | /** 是否选中*/ 387 | @objc public var isSelected: Bool = false { 388 | willSet { 389 | imageView.image = newValue ? selectIedImage : normalImage 390 | } 391 | } 392 | 393 | /** 是否能操作*/ 394 | @objc public var isEdit: Bool = true 395 | 396 | /** image 和title 的间距 默认为8.0 ,设置image时生效*/ 397 | @objc public var contentPadding: CGFloat = 8.0 398 | 399 | /** 每个tag 最小高度 default is 0 */ 400 | @objc public var minHeight: CGFloat = 0 401 | 402 | /** 每个tag的边距 default is top:5,letf:5,bottom:5,right:5*/ 403 | @objc public var contentInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) 404 | 405 | /** 装载view*/ 406 | @objc public var contentView = UIView() 407 | 408 | /** 标题*/ 409 | @objc public var titleLabel = UILabel() 410 | 411 | /** 图片的位置*/ 412 | @objc public var imageAlignmentMode : TagImageViewAlignmentMode = .right 413 | 414 | /** 图片*/ 415 | @objc public var imageView = UIImageView() 416 | 417 | /** 默认为 image 大小*/ 418 | fileprivate var imageWidth: CGFloat { 419 | guard let imageW = imageView.image?.size.width else { 420 | return 0.0 421 | } 422 | if imageView.frame != CGRect.zero, imageView.image != nil { 423 | return imageView.frame.width 424 | } 425 | return imageW 426 | } 427 | 428 | fileprivate var imageHeight: CGFloat { 429 | guard let imageH = imageView.image?.size.height else { 430 | return 0.0 431 | } 432 | if imageView.frame != CGRect.zero, imageView.image != nil { 433 | return imageView.frame.height 434 | } 435 | return imageH 436 | } 437 | 438 | fileprivate var tagContentPadding: CGFloat { 439 | get { 440 | return imageWidth > 0.0 ? contentPadding : 0.0 441 | } 442 | } 443 | 444 | public override init() { 445 | contentView.addSubview(titleLabel) 446 | contentView.addSubview(imageView) 447 | /** 每个tagtext 默认值*/ 448 | contentView.backgroundColor = .darkGray 449 | contentView.layer.masksToBounds = true 450 | contentView.layer.cornerRadius = 5 451 | titleLabel.font = UIFont.systemFont(ofSize: 14) 452 | titleLabel.text = "" 453 | titleLabel.textColor = .white 454 | titleLabel.numberOfLines = 0 455 | imageSize = CGSize.zero 456 | super.init() 457 | } 458 | 459 | } 460 | 461 | extension Bundle { 462 | 463 | fileprivate static func getBundle(bundleName: String) -> Bundle { 464 | let path = Bundle(for: LJTagsView.classForCoder()).path(forResource: bundleName, ofType: "bundle") ?? "" 465 | let bundle = Bundle(path: path) 466 | return bundle! 467 | } 468 | } 469 | 470 | extension UIImage { 471 | 472 | fileprivate static func imageFormBundle(with imageName: String) -> UIImage? { 473 | let bundle = Bundle.getBundle(bundleName: "LJTagsView") 474 | let name = imageName + "@2x" 475 | let path = bundle.path(forResource: name, ofType: "png") ?? "" 476 | let image = UIImage.init(contentsOfFile: path) 477 | return image 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /LJTagsView/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // LJTagsView 4 | // 5 | // Created by IMS_Mac on 2021/3/10. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '10.0' 3 | 4 | target 'LJTagsView' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | pod 'SnapKit' 9 | 10 | # Pods for LJTagsView 11 | 12 | end 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LJTagsView 2 | A lightweight label class, adaptive height, width. 3 | 4 | ## 使用场景 5 | 6 | ![image](https://github.com/Clemmie-L/LJTagsView/blob/main/image/ezgif-2-923de88307ee.gif) 7 | 8 | ## 使用方式 9 | 10 | pod 'LJTagsView' 11 | 12 | ### 初始化 13 | 14 | tagsView0.backgroundColor = .brown 15 | tagsView0.tagsViewDelegate = self 16 | tagsView0.tagsViewContentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 17 | tagsView0.tagsViewMinHeight = 40 18 | tagsView0.scrollDirection = .horizontal 19 | tagsView0.tagsViewMaxHeight = 300 20 | tagsView0.minimumLineSpacing = 30; 21 | tagsView0.minimumInteritemSpacing = 30; 22 | self.view.addSubview(tagsView0) 23 | 24 | tagsView0.snp.makeConstraints { (make) in 25 | make.top.equalToSuperview().offset(150) 26 | make.left.equalToSuperview().offset(0) 27 | make.width.equalTo(414) 28 | 29 | } 30 | 31 | tagsView0.dataSource = ["Listing ID","Tower","Sole Agency Type","Have Keys","New Development","Tags","Big landlord","Street Address","Currency","Price per unit","Price per unit(Gross)","Price per unit(Saleable)","Size(Gross)","Size(Saleable)","Status","Register","Landlord","SSD","Agent","Floor Alias","Unit Alias","Unit Balcony","Tower Type","Building Age","Segment","Car Park","Is Coop","SPV","Pet Friendly","View Type","Property Type"] 32 | 33 | // 最后reload 34 | tagsView0.reloadData() 35 | 36 | ### delegate 37 | 38 | /** 设置每个tag的属性,包含UI ,对应的属性*/ 39 | func tagsViewUpdatePropertyModel(_ tagsView: LJTagsView, item: TagsPropertyModel, index: NSInteger) { 40 | item.contentView.backgroundColor = UIColor.darkGray 41 | item.contentInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10) 42 | item.normalImage = UIImage(named: "select_not") 43 | item.selectIedImage = UIImage(named: "selected") 44 | item.imageSize = CGSize(width: 10, height: 10) 45 | item.imageAlignmentMode = .left 46 | } 47 | 48 | // 点击事情 49 | func tagsViewItemTapAction(_ tagsView: LJTagsView, item: TagsPropertyModel, index: NSInteger) { 50 | // 删除事件 51 | dataSource.remove(at: index) 52 | tagsView.dataSource.remove(at: index) 53 | tagsView.reloadData() 54 | } 55 | 56 | // 返回高度 57 | func tagsViewUpdateHeight(_ tagsView: LJTagsView, sumHeight: CGFloat) { 58 | 59 | // do something 60 | 61 | } 62 | 63 | ## 版本描述 64 | ### 1.0.0 初始版 65 | ### 1.0.1 优化optional protocol 的声明 66 | ### 1.0.2 添加showline 属性 :实现展开和收合功能 67 | ### 1.0.3 添加[TagsPropertyModel] 数据源,方便初始化 68 | ### 1.0.4 修复每个tag 的minHeight 不统一造成布局错乱 69 | ### 1.0.6 适配OC;添加每个item的是否能点击属性:isEdit 70 | ### 1.0.7 添加默认资源 71 | ### 1.0.8 修复资源路径不对,导致无法加载资源 72 | -------------------------------------------------------------------------------- /image/ezgif-2-923de88307ee.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clemmie-L/LJTagsView/66d12f38605631fa7f2fc55fdbee2a570c3c5b62/image/ezgif-2-923de88307ee.gif --------------------------------------------------------------------------------