Skip to content

Commit

Permalink
[#21] 키워드 버튼 뷰
Browse files Browse the repository at this point in the history
Flow Layout 을 편리하게 사용하기위해 https://swiftuirecipes.com/blog/flow-layout-in-swiftui 의 코드를 가져와보았습니다.
  • Loading branch information
seungwook-jung committed Dec 22, 2020
1 parent 9eaa5d7 commit d08a1e5
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 28 deletions.
21 changes: 12 additions & 9 deletions HunnitLog/HunnitLog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
63CA1935257BBCF300CB2283 /* AchievementBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CA1934257BBCF300CB2283 /* AchievementBar.swift */; };
63CA193A257BC5E300CB2283 /* BottomNextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CA1939257BC5E300CB2283 /* BottomNextButton.swift */; };
63F09B49256E9E5A00EB9101 /* AchievementRateModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F09B48256E9E5A00EB9101 /* AchievementRateModalView.swift */; };
E030BAFC256D0A5800EC18EC /* KeyWordButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E030BAFB256D0A5800EC18EC /* KeyWordButton.swift */; };
E030BAFC256D0A5800EC18EC /* TagButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E030BAFB256D0A5800EC18EC /* TagButton.swift */; };
E034A5DE25887CF30092A050 /* FlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E034A5DD25887CF30092A050 /* FlowView.swift */; };
E034A5E6258881530092A050 /* TagButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E034A5E5258881530092A050 /* TagButtonViewModel.swift */; };
E051ABAE257B5CCB00E06D52 /* AchievementQuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051ABAD257B5CCB00E06D52 /* AchievementQuestionView.swift */; };
E05859792574A05A00F5AE0C /* AchievementDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05859782574A05A00F5AE0C /* AchievementDateView.swift */; };
E058597E2575CF6F00F5AE0C /* AchievementListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E058597D2575CF6F00F5AE0C /* AchievementListView.swift */; };
Expand All @@ -57,9 +59,6 @@
E0B1887D25765B71003DF180 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E0B1887C25765B71003DF180 /* Images.xcassets */; };
E0B18882257662C0003DF180 /* RetrospectQuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B18881257662C0003DF180 /* RetrospectQuestionView.swift */; };
E0B18887257662CE003DF180 /* RetrospectQuestionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B18886257662CE003DF180 /* RetrospectQuestionViewModel.swift */; };
E0A0AFD1257E2A0D0030F3F6 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = E0A0AFD0257E2A0D0030F3F6 /* Lottie */; };
E0A0AFD7257E2A2B0030F3F6 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A0AFD6257E2A2B0030F3F6 /* SplashView.swift */; };
E0A0AFDF257E2BBB0030F3F6 /* splash.json in Resources */ = {isa = PBXBuildFile; fileRef = E0A0AFDE257E2BBB0030F3F6 /* splash.json */; };
E0B91481256158AA00F93987 /* Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9147E256158AA00F93987 /* Tracker.swift */; };
E0B91482256158AA00F93987 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9147F256158AA00F93987 /* APIService.swift */; };
E0B91483256158AA00F93987 /* APIServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B91480256158AA00F93987 /* APIServiceError.swift */; };
Expand Down Expand Up @@ -126,7 +125,9 @@
63CA1934257BBCF300CB2283 /* AchievementBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementBar.swift; sourceTree = "<group>"; };
63CA1939257BC5E300CB2283 /* BottomNextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomNextButton.swift; sourceTree = "<group>"; };
63F09B48256E9E5A00EB9101 /* AchievementRateModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementRateModalView.swift; sourceTree = "<group>"; };
E030BAFB256D0A5800EC18EC /* KeyWordButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyWordButton.swift; sourceTree = "<group>"; };
E030BAFB256D0A5800EC18EC /* TagButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagButton.swift; sourceTree = "<group>"; };
E034A5DD25887CF30092A050 /* FlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowView.swift; sourceTree = "<group>"; };
E034A5E5258881530092A050 /* TagButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagButtonViewModel.swift; sourceTree = "<group>"; };
E051ABAD257B5CCB00E06D52 /* AchievementQuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementQuestionView.swift; sourceTree = "<group>"; };
E05859782574A05A00F5AE0C /* AchievementDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementDateView.swift; sourceTree = "<group>"; };
E058597D2575CF6F00F5AE0C /* AchievementListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementListView.swift; sourceTree = "<group>"; };
Expand All @@ -138,8 +139,6 @@
E0B1887C25765B71003DF180 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
E0B18881257662C0003DF180 /* RetrospectQuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectQuestionView.swift; sourceTree = "<group>"; };
E0B18886257662CE003DF180 /* RetrospectQuestionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectQuestionViewModel.swift; sourceTree = "<group>"; };
E0A0AFD6257E2A2B0030F3F6 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
E0A0AFDE257E2BBB0030F3F6 /* splash.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = splash.json; sourceTree = "<group>"; };
E0B9147E256158AA00F93987 /* Tracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tracker.swift; sourceTree = "<group>"; };
E0B9147F256158AA00F93987 /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = "<group>"; };
E0B91480256158AA00F93987 /* APIServiceError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIServiceError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -339,9 +338,10 @@
E05859782574A05A00F5AE0C /* AchievementDateView.swift */,
E058597D2575CF6F00F5AE0C /* AchievementListView.swift */,
E051ABAD257B5CCB00E06D52 /* AchievementQuestionView.swift */,
E030BAFB256D0A5800EC18EC /* KeyWordButton.swift */,
E030BAFB256D0A5800EC18EC /* TagButton.swift */,
E0B18871257659BC003DF180 /* AchievementItemView.swift */,
E0B18881257662C0003DF180 /* RetrospectQuestionView.swift */,
E034A5DD25887CF30092A050 /* FlowView.swift */,
);
path = AchievementSetup;
sourceTree = "<group>";
Expand Down Expand Up @@ -371,6 +371,7 @@
children = (
E0B1886B2576592D003DF180 /* AchievementItemViewModel.swift */,
E0B18886257662CE003DF180 /* RetrospectQuestionViewModel.swift */,
E034A5E5258881530092A050 /* TagButtonViewModel.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -544,11 +545,12 @@
E058597E2575CF6F00F5AE0C /* AchievementListView.swift in Sources */,
E0B18882257662C0003DF180 /* RetrospectQuestionView.swift in Sources */,
562F69B3256ECC3A00C50C97 /* GoalRow.swift in Sources */,
E030BAFC256D0A5800EC18EC /* KeyWordButton.swift in Sources */,
E030BAFC256D0A5800EC18EC /* TagButton.swift in Sources */,
632A10762566B9EB0085B126 /* AchievementRow.swift in Sources */,
632A106E2566B94C0085B126 /* ShadowCard.swift in Sources */,
63A4DF25256C1B0700257ED2 /* CustomAlertHostingController.swift in Sources */,
635D5F6E25728D4400FEB3EF /* AchievementModalRow.swift in Sources */,
E034A5DE25887CF30092A050 /* FlowView.swift in Sources */,
630EBD102566954500CDFA50 /* HalfableGrayLine.swift in Sources */,
562F6978256A7A6D00C50C97 /* CustomColor.swift in Sources */,
562F69AE256ECBD400C50C97 /* AchievedGoalRow.swift in Sources */,
Expand Down Expand Up @@ -586,6 +588,7 @@
635D5F63257285C500FEB3EF /* AchievementModalPercentageView.swift in Sources */,
562F69A4256EC24800C50C97 /* ProgressBar.swift in Sources */,
63A4DF3D256C1B5600257ED2 /* View+Extension.swift in Sources */,
E034A5E6258881530092A050 /* TagButtonViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
14 changes: 14 additions & 0 deletions HunnitLog/HunnitLog/Model/TagButtonViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// TagButtonViewModel.swift
// HunnitLog
//
// Created by seungwook.jung on 2020/12/15.
//

import Foundation

struct TagButtonViewModel: Identifiable, Hashable {
let id: UUID = UUID()
let title: String
var isSelected: Bool = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,22 @@ struct AchievementListView: View {
@State var isLinkActive: Bool = false
@State var achievementItems: [AchievementItemViewModel] = [
AchievementItemViewModel(editable: false, title: "안녕안녕"),
AchievementItemViewModel(editable: false, title: "안녕안녕"),
AchievementItemViewModel(editable: false, title: "안녕안녕"),
]
@State var newItem: AchievementItemViewModel = AchievementItemViewModel(editable: true, title: "")

var tagButtonItems: [TagButtonViewModel] = [
TagButtonViewModel(title: "자격증 따기"),
TagButtonViewModel(title: "학점 4.0 넘기"),
TagButtonViewModel(title: "일찍 일어나기"),
TagButtonViewModel(title: "몸무게 40kg 진입"),
TagButtonViewModel(title: "자격증 따기"),
TagButtonViewModel(title: "자격증 따기"),
TagButtonViewModel(title: "학점 4.0 넘기"),
TagButtonViewModel(title: "일찍 일어나기"),
]

var body: some View {
VStack {
ScrollView(showsIndicators: false) {
Expand Down Expand Up @@ -62,12 +75,10 @@ struct AchievementListView: View {
action: {

})

Button(action: {
self.newItem.editable = false
self.achievementItems.append(self.newItem)
self.newItem = AchievementItemViewModel(editable: true, title: "")

}) {
Text("+")
.font(Constants.titleFont)
Expand All @@ -80,6 +91,16 @@ struct AchievementListView: View {

Text("이런 목표들은 어때요?")
.padding(.bottom, 20)

FlowView(mode: .scrollable,
binding: .constant(5),
items: self.tagButtonItems) { item in
TagButton(item: item) {
// TODO : - 버튼 선택시 액션
print($0)
}
}.padding()

}
}
BottomNextButton(geometry: self.geometry,
Expand Down
85 changes: 85 additions & 0 deletions HunnitLog/HunnitLog/View/AchievementSetup/FlowView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// FlowView.swift
// HunnitLog
//
// https://swiftuirecipes.com/blog/flow-layout-in-swiftui
//

import SwiftUI

struct FlowView<B, T: Hashable, V: View>: View {
let mode: Mode
@Binding var binding: B
let items: [T]
let viewMapping: (T) -> V

@State private var totalHeight: CGFloat

init(mode: Mode, binding: Binding<B>, items: [T], viewMapping: @escaping (T) -> V) {
self.mode = mode
_binding = binding
self.items = items
self.viewMapping = viewMapping
_totalHeight = State(initialValue: (mode == .scrollable) ? .zero : .infinity)
}

var body: some View {
let stack = VStack {
GeometryReader { geometry in
self.content(in: geometry)
}
}
return Group {
if self.mode == .scrollable {
stack.frame(height: self.totalHeight)
} else {
stack.frame(maxHeight: self.totalHeight)
}
}
}

private func content(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(self.items, id: \.self) { item in
self.viewMapping(item)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if item == self.items.last {
width = 0
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: { d in
let result = height
if item == self.items.last {
height = 0
}
return result
})
}
}
.background(viewHeightReader($totalHeight))
}

private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { geo -> Color in
DispatchQueue.main.async {
binding.wrappedValue = geo.frame(in: .local).size.height
}
return .clear
}
}

enum Mode {
case scrollable, vstack
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// TagView.swift
// TagButton.swift
// HunnitLog
//
// Created by seungwook.jung on 2020/11/24.
Expand All @@ -8,10 +8,8 @@
import SwiftUI

struct TagButton: View {
private let title: String
private let buttonAction: (() -> Void)

@State var isSelected: Bool = false
@State var item: TagButtonViewModel
let buttonAction: ((TagButtonViewModel) -> Void)

private enum Constants {
static let cornerRadius: CGFloat = 9
Expand All @@ -22,22 +20,20 @@ struct TagButton: View {
static let letterSpacing: CGFloat = -0.39
}

init(title: String, action: @escaping (() -> Void)) {
self.title = title
self.buttonAction = action
}

var body: some View {
Button(action: self.buttonAction) {
Text(self.title)
Button(action: {
self.item.isSelected = !self.item.isSelected
self.buttonAction(self.item)
}) {
Text(self.item.title)
.font(Constants.titleFont)
.kerning(Constants.letterSpacing)
.padding(EdgeInsets(top: Constants.verticalPadding,
leading: Constants.horizontalPadding,
bottom: Constants.verticalPadding,
trailing: Constants.horizontalPadding))
.background(self.isSelected ? CustomColor.gray : Color.clear)
.foregroundColor(self.isSelected ? .white : CustomColor.darkGray)
.background(self.item.isSelected ? CustomColor.gray : Color.clear)
.foregroundColor(self.item.isSelected ? .white : CustomColor.darkGray)
.cornerRadius(Constants.cornerRadius)
.overlay(RoundedRectangle(cornerRadius: Constants.cornerRadius)
.stroke(CustomColor.gray,
Expand All @@ -47,10 +43,10 @@ struct TagButton: View {
}

struct TagView_Previews: PreviewProvider {
static let isSelected = false
static var previews: some View {
TagButton(title: "자격증 따기") {
print("action")
var model: TagButtonViewModel = TagButtonViewModel(title: "자격증 따기")
TagButton(item: model) {
print($0)
}
}
}

0 comments on commit d08a1e5

Please sign in to comment.