├── .gitignore
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── TagLayoutView
│ └── TagLayoutView.swift
├── TagLayoutView.gif
└── TagLayoutView.podspec
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 | .DS_Store
92 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 giiiita
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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "TagLayoutView",
6 | platforms: [
7 | .iOS(.v13)
8 | ],
9 | products: [
10 | .library(
11 | name: "TagLayoutView",
12 | targets: ["TagLayoutView"])
13 | ],
14 | dependencies: [],
15 | targets: [
16 | .target(
17 | name: "TagLayoutView",
18 | dependencies: [])
19 | ]
20 | )
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TagLayoutView
2 | TagLayoutViewTagLayoutView is a library made with SwiftUI for easy tag display.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## Usage
13 | ```swift
14 | struct TagListView: View {
15 | var body: some View {
16 | GeometryReader { geometry in
17 | TagLayoutView(
18 | ["tagA", "tagB", "tagC"],
19 | tagFont: UIFont.systemFont(ofSize: 34, weight: UIFont.Weight.thin)
20 | padding: 20,
21 | parentWidth: geometry.size.width) { tag in
22 | Text(tag)
23 | .bold()
24 | .fixedSize()
25 | .padding(EdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 12))
26 | .foregroundColor(Color.green)
27 | .background(Color.white)
28 | .overlay(RoundedRectangle(cornerRadius: 32).stroke(Color.green, lineWidth: 2.0))
29 | }.padding(.all, 16)
30 | }
31 | }
32 | }
33 | ```
34 |
35 | ## Installation
36 |
37 | `TagLayoutView` is available via [Swift Package Manager](https://swift.org/package-manager).
38 |
39 | Using Xcode 11, go to `File -> Swift Packages -> Add Package Dependency` and enter [https://github.com/yotsu12/TagLayoutView](https://github.com/yotsu12/TagLayoutView)
40 |
--------------------------------------------------------------------------------
/Sources/TagLayoutView/TagLayoutView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagLayoutView.swift
3 | // Created by giiiita on 2020/02/17.
4 | // Copyright © 2020 giiiita. All rights reserved.
5 | //
6 |
7 | #if arch(x86_64) || arch(arm64)
8 |
9 | import SwiftUI
10 |
11 | @available(iOS 13.0, *)
12 | public struct TagLayoutView: View where Content: View {
13 |
14 | private let tags: [String]
15 | private var tagFont: UIFont
16 | private let padding: CGFloat
17 | private let parentWidth: CGFloat
18 | private let content: (String) -> Content
19 | private var elementsCountByRow: [Int] = []
20 |
21 | public init(_ tags: [String],
22 | tagFont: UIFont,
23 | padding: CGFloat,
24 | parentWidth: CGFloat,
25 | content: @escaping (String) -> Content) {
26 | self.tags = tags
27 | self.tagFont = tagFont
28 | self.padding = padding
29 | self.parentWidth = parentWidth
30 | self.content = content
31 | self.elementsCountByRow = self.getElementsCountByRow(self.parentWidth)
32 | }
33 |
34 | private func getElementsCountByRow(_ rowSize: CGFloat) -> [Int] {
35 | let tagWidths = self.tags.map{$0.widthOfString(usingFont: self.tagFont)}
36 |
37 | var currentRowTotalWidth: CGFloat = 0.0
38 | var currentRowElementsCount: Int = 0
39 | var result: [Int] = []
40 |
41 | for tagWidth in tagWidths {
42 | let fixedTagWidth = tagWidth + (2 * self.padding)
43 | if currentRowTotalWidth + fixedTagWidth <= rowSize {
44 | currentRowTotalWidth += fixedTagWidth
45 | currentRowElementsCount += 1
46 | guard result.count != 0 else { result.append(1); continue }
47 | result[result.count - 1] = currentRowElementsCount
48 | } else {
49 | currentRowTotalWidth = fixedTagWidth
50 | currentRowElementsCount = 1
51 | result.append(1)
52 | }
53 | }
54 | return result
55 | }
56 |
57 | private func getTag(elementsCountByRow: [Int], rowIndex: Int, elementIndex: Int) -> String {
58 | let sumOfPreviousRows = elementsCountByRow.enumerated().reduce(0) { total, next in
59 | if next.offset < rowIndex {
60 | return total + next.element
61 | } else {
62 | return total
63 | }
64 | }
65 | let orderedTagsIndex = sumOfPreviousRows + elementIndex
66 | guard self.tags.count > orderedTagsIndex else { return "" }
67 | return self.tags[orderedTagsIndex]
68 | }
69 |
70 | public var body : some View {
71 | VStack(alignment: .leading, spacing: 0) {
72 | ForEach(0 ..< self.elementsCountByRow.count, id: \.self) { rowIndex in
73 | HStack {
74 | ForEach(0 ..< self.elementsCountByRow[rowIndex], id: \.self) { elementIndex in
75 | self.content(self.getTag(elementsCountByRow: self.elementsCountByRow, rowIndex: rowIndex, elementIndex: elementIndex))
76 | }
77 | Spacer()
78 | }.padding(.vertical, 4)
79 | }
80 | }
81 | }
82 | }
83 |
84 | extension String {
85 |
86 | func widthOfString(usingFont font: UIFont) -> CGFloat {
87 | let fontAttributes = [NSAttributedString.Key.font: font]
88 | let size = self.size(withAttributes: fontAttributes)
89 | return size.width
90 | }
91 |
92 | }
93 |
94 | #endif
95 |
--------------------------------------------------------------------------------
/TagLayoutView.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giiiita/TagLayoutView/904a146de13f1ef6468a8d3644a51f84a530752d/TagLayoutView.gif
--------------------------------------------------------------------------------
/TagLayoutView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'TagLayoutView'
3 | s.version = '1.0.0'
4 | s.summary = 'SwiftUI library for easy tag display'
5 | s.description = <<-DESC
6 | TagLayoutView enables you to display tags easily.
7 | DESC
8 | s.homepage = 'https://github.com/giiiita/TagLayoutView'
9 |
10 | s.license = { :type => 'MIT', :file => 'LICENSE' }
11 | s.author = { 'giiiita' => '' }
12 | s.source = { :git => 'https://github.com/giiiita/TagLayoutView', :tag => s.version.to_s }
13 |
14 | s.ios.deployment_target = '12.0'
15 |
16 | s.swift_version = '5.0'
17 | s.source_files = 'Sources/TagLayoutView/**/*'
18 | end
19 |
--------------------------------------------------------------------------------