Skip to content

Commit

Permalink
Merge pull request #14 from tevelee/feature/justification
Browse files Browse the repository at this point in the history
Justified layout
  • Loading branch information
tevelee authored Jun 24, 2024
2 parents b576f3c + 8de7db9 commit fb5083e
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 139 deletions.
57 changes: 50 additions & 7 deletions Sources/Flow/Example/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import SwiftUI

@available(macOS 13.0, *)
@available(macOS 14.0, *)
struct ContentView: View {
@State private var axis: Axis = .horizontal
@State private var contents: Contents = .boxes
@State private var width: CGFloat = 400
@State private var height: CGFloat = 400
@State private var itemSpacing: CGFloat? = nil
@State private var lineSpacing: CGFloat? = nil
@State private var justified: Justified = .none
@State private var horizontalAlignment: HAlignment = .center
@State private var verticalAlignment: VAlignment = .center
private let items: [String] = "This is a long text that wraps nicely in flow layout".components(separatedBy: " ")
private let texts = "This is a long text that wraps nicely in flow layout".components(separatedBy: " ").map { string in
AnyView(Text(string))
}
private let colors = [Color.red, .orange, .yellow, .mint, .green, .teal, .blue, .purple, .indigo].map { color in
AnyView(color.frame(height: 30).frame(minWidth: 30))
}

enum HAlignment: String, Hashable, CaseIterable, CustomStringConvertible {
case leading, center, trailing
Expand Down Expand Up @@ -39,6 +46,9 @@ struct ContentView: View {
var body: some View {
NavigationSplitView(columnVisibility: .constant(.all)) {
List {
Section(header: Text("Content")) {
picker($contents)
}
Section(header: Text("Layout")) {
picker($axis)
}
Expand Down Expand Up @@ -68,19 +78,27 @@ struct ContentView: View {
stepper("Item", $itemSpacing)
stepper("Line", $lineSpacing)
}
Section(header: Text("Justification")) {
picker($justified)
}
}
.listStyle(.sidebar)
.frame(minWidth: 250)
.frame(minWidth: 280)
.navigationTitle("Flow Layout")
.padding()
} detail: {
layout {
ForEach(items, id: \.self, content: Text.init)
let views: [AnyView] = switch contents {
case .texts: texts
case .boxes: colors
}
ForEach(Array(views.enumerated()), id: \.offset) { $0.element.border(.blue) }
}
.border(.red.opacity(0.2))
.frame(maxWidth: width, maxHeight: height)
.border(.red)
}
.frame(minWidth: 600, minHeight: 500)
}

private func stepper(_ title: String, _ selection: Binding<CGFloat?>) -> some View {
Expand Down Expand Up @@ -115,22 +133,47 @@ struct ContentView: View {
HFlow(
alignment: verticalAlignment.value,
itemSpacing: itemSpacing,
rowSpacing: lineSpacing
rowSpacing: lineSpacing,
justification: justified.justification
)
)
case .vertical:
return AnyLayout(
VFlow(
alignment: horizontalAlignment.value,
itemSpacing: itemSpacing,
columnSpacing: lineSpacing
columnSpacing: lineSpacing,
justification: justified.justification
)
)
}
}
}

@available(macOS 13.0, *)
enum Contents: String, CustomStringConvertible, CaseIterable {
case texts
case boxes

var description: String { rawValue }
}

enum Justified: String, CustomStringConvertible, CaseIterable {
case none
case stretchItems
case stretchSpaces

var description: String { rawValue }

var justification: Justification? {
switch self {
case .none: nil
case .stretchItems: .stretchItems
case .stretchSpaces: .stretchSpaces
}
}
}

@available(macOS 14.0, *)
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
Expand Down
94 changes: 63 additions & 31 deletions Sources/Flow/HFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,23 @@ public struct HFlow<Content: View>: View {
/// want the flow to choose a default distance for each pair of subviews.
/// - rowSpacing: The distance between rows of subviews, or `nil` if you
/// want the flow to choose a default distance for each pair of rows.
/// - justification: Whether the layout should fill the remaining
/// available space in each row by stretching either items or spaces.
/// - content: A view builder that creates the content of this flow.
public init(alignment: VerticalAlignment = .center,
itemSpacing: CGFloat? = nil,
rowSpacing: CGFloat? = nil,
@ViewBuilder content contentBuilder: () -> Content) {
public init(
alignment: VerticalAlignment = .center,
itemSpacing: CGFloat? = nil,
rowSpacing: CGFloat? = nil,
justification: Justification? = nil,
@ViewBuilder content contentBuilder: () -> Content
) {
content = contentBuilder()
layout = HFlowLayout(alignment: alignment,
itemSpacing: itemSpacing,
rowSpacing: rowSpacing)
layout = HFlowLayout(
alignment: alignment,
itemSpacing: itemSpacing,
rowSpacing: rowSpacing,
justification: justification
)
}

/// Creates a horizontal flow with the give spacing and vertical alignment.
Expand All @@ -50,14 +58,22 @@ public struct HFlow<Content: View>: View {
/// guide has the same vertical screen coordinate for every child view.
/// - spacing: The distance between adjacent subviews, or `nil` if you
/// want the flow to choose a default distance for each pair of subviews.
/// - justification: Whether the layout should fill the remaining
/// available space in each row by stretching either items or spaces.
/// - content: A view builder that creates the content of this flow.
public init(alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil,
@ViewBuilder content contentBuilder: () -> Content) {
self.init(alignment: alignment,
itemSpacing: spacing,
rowSpacing: spacing,
content: contentBuilder)
public init(
alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil,
justification: Justification? = nil,
@ViewBuilder content contentBuilder: () -> Content
) {
self.init(
alignment: alignment,
itemSpacing: spacing,
rowSpacing: spacing,
justification: justification,
content: contentBuilder
)
}

public var body: some View {
Expand All @@ -83,12 +99,20 @@ extension HFlow: Layout where Content == EmptyView {
/// want the flow to choose a default distance for each pair of subviews.
/// - rowSpacing: The distance between rows of subviews, or `nil` if you
/// want the flow to choose a default distance for each pair of rows.
public init(alignment: VerticalAlignment = .center,
itemSpacing: CGFloat? = nil,
rowSpacing: CGFloat? = nil) {
self.init(alignment: alignment,
itemSpacing: itemSpacing,
rowSpacing: rowSpacing) {
/// - justification: Whether the layout should fill the remaining
/// available space in each row by stretching either items or spaces.
public init(
alignment: VerticalAlignment = .center,
itemSpacing: CGFloat? = nil,
rowSpacing: CGFloat? = nil,
justification: Justification? = nil
) {
self.init(
alignment: alignment,
itemSpacing: itemSpacing,
rowSpacing: rowSpacing,
justification: justification
) {
EmptyView()
}
}
Expand All @@ -100,25 +124,33 @@ extension HFlow: Layout where Content == EmptyView {
/// guide has the same vertical screen coordinate for every child view.
/// - spacing: The distance between adjacent subviews, or `nil` if you
/// want the flow to choose a default distance for each pair of subviews.
public init(alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil) {
self.init(alignment: alignment,
spacing: spacing) {
public init(
alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil
) {
self.init(
alignment: alignment,
spacing: spacing
) {
EmptyView()
}
}

public func sizeThatFits(proposal: ProposedViewSize, subviews: LayoutSubviews, cache: inout ()) -> CGSize {
layout.sizeThatFits(proposal: proposal,
subviews: subviews,
cache: &cache)
layout.sizeThatFits(
proposal: proposal,
subviews: subviews,
cache: &cache
)
}

public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: LayoutSubviews, cache: inout ()) {
layout.placeSubviews(in: bounds,
proposal: proposal,
subviews: subviews,
cache: &cache)
layout.placeSubviews(
in: bounds,
proposal: proposal,
subviews: subviews,
cache: &cache
)
}

public static var layoutProperties: LayoutProperties {
Expand Down
16 changes: 10 additions & 6 deletions Sources/Flow/Internal/HFlowLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ public struct HFlowLayout {
private let layout: FlowLayout

public init(
alignment: VerticalAlignment,
itemSpacing: CGFloat?,
rowSpacing: CGFloat?
alignment: VerticalAlignment = .center,
itemSpacing: CGFloat? = nil,
rowSpacing: CGFloat? = nil,
justification: Justification? = nil
) {
layout = .horizontal(alignment: alignment,
itemSpacing: itemSpacing,
lineSpacing: rowSpacing)
layout = .horizontal(
alignment: alignment,
itemSpacing: itemSpacing,
lineSpacing: rowSpacing,
justification: justification
)
}
}

Expand Down
Loading

0 comments on commit fb5083e

Please sign in to comment.