Skip to content

Commit

Permalink
Merge pull request #684 from rechsteiner/indicator-style
Browse files Browse the repository at this point in the history
Add custom style for indicator view when using SwiftUI
  • Loading branch information
rechsteiner authored May 23, 2023
2 parents 98178de + a050a11 commit 9efa304
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 6 deletions.
59 changes: 59 additions & 0 deletions ExampleSwiftUI/CustomIndicatorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Parchment
import SwiftUI
import UIKit

struct CustomIndicatorView: View {
var body: some View {
PageView {
Page("Scone") {
Text("Scone")
.font(.largeTitle)
.foregroundColor(.gray)
}

Page("Cinnamon Roll") {
Text("Cinnamon Roll")
.font(.largeTitle)
.foregroundColor(.gray)
}

Page("Croissant") {
Text("Croissant")
.font(.largeTitle)
.foregroundColor(.gray)
}

Page("Muffin") {
Text("Muffin")
.font(.largeTitle)
.foregroundColor(.gray)
}
}
.borderColor(.black.opacity(0.1))
.indicatorOptions(.visible(height: 2))
.indicatorStyle(SquigglyIndicatorStyle())

}
}

struct SquigglyIndicatorStyle: PagingIndicatorStyle {
func makeBody(configuration: Configuration) -> some View {
SquigglyShape()
.stroke(.blue, style: StrokeStyle(lineWidth: 3, lineCap: .round))
}
}

struct SquigglyShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: .zero)

for x in stride(from: 0, through: rect.width, by: 1) {
let sine = sin(x / 1.5)
let y = rect.height * sine
path.addLine(to: CGPoint(x: x, y: y))
}

return path
}
}
1 change: 1 addition & 0 deletions ExampleSwiftUI/ExampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct ExampleApp: App {
NavigationLink("Lifecycle events", destination: LifecycleView())
NavigationLink("Change items", destination: ChangeItemsView())
NavigationLink("Dynamic items", destination: DynamicItemsView())
NavigationLink("Custom indicator", destination: CustomIndicatorView())
}
}
.navigationBarTitleDisplayMode(.inline)
Expand Down
24 changes: 18 additions & 6 deletions Parchment.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
952D802F1E37CC09003DCB18 /* PagingTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952D802E1E37CC09003DCB18 /* PagingTransition.swift */; };
9530E25329DEC2E5004FC88C /* PageContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9530E25229DEC2E5004FC88C /* PageContentConfiguration.swift */; };
953B8D352416C3DC0047BBA1 /* SelfSizingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 953B8D342416C3DC0047BBA1 /* SelfSizingViewController.swift */; };
9546B2AB2A1D2F06000390C6 /* PagingHostingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9546B2AA2A1D2F06000390C6 /* PagingHostingIndicatorView.swift */; };
9546B2AD2A1D44EF000390C6 /* CustomIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9546B2AC2A1D44EF000390C6 /* CustomIndicatorView.swift */; };
9546B2AF2A1D4767000390C6 /* PagingIndicatorStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9546B2AE2A1D4767000390C6 /* PagingIndicatorStyle.swift */; };
954842591F42438E0072038C /* PagingInvalidationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954842581F42438E0072038C /* PagingInvalidationContext.swift */; };
9548425D1F42486B0072038C /* PagingDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9548425C1F42486B0072038C /* PagingDiff.swift */; };
954842631F4252070072038C /* PagingDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954842621F4252070072038C /* PagingDiffTests.swift */; };
Expand Down Expand Up @@ -265,6 +268,9 @@
952D802E1E37CC09003DCB18 /* PagingTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagingTransition.swift; sourceTree = "<group>"; };
9530E25229DEC2E5004FC88C /* PageContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageContentConfiguration.swift; sourceTree = "<group>"; };
953B8D342416C3DC0047BBA1 /* SelfSizingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingViewController.swift; sourceTree = "<group>"; };
9546B2AA2A1D2F06000390C6 /* PagingHostingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingHostingIndicatorView.swift; sourceTree = "<group>"; };
9546B2AC2A1D44EF000390C6 /* CustomIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomIndicatorView.swift; sourceTree = "<group>"; };
9546B2AE2A1D4767000390C6 /* PagingIndicatorStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingIndicatorStyle.swift; sourceTree = "<group>"; };
954842581F42438E0072038C /* PagingInvalidationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingInvalidationContext.swift; sourceTree = "<group>"; };
9548425C1F42486B0072038C /* PagingDiff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingDiff.swift; sourceTree = "<group>"; };
954842601F4251F90072038C /* PagingViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingViewControllerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -455,13 +461,17 @@
3E4090A31C88BD1700800E22 /* Classes */ = {
isa = PBXGroup;
children = (
955453C32413C80F00923BC8 /* PageViewController.swift */,
956FFFCF29BE23F900477E94 /* PageViewCoordinator.swift */,
95FEEA4E2423F213009B5B64 /* PageViewManager.swift */,
3E4090A91C88BDD100800E22 /* PagingBorderLayoutAttributes.swift */,
3E49C71F1C8F5C13006269DD /* PagingBorderView.swift */,
3E49C7201C8F5C13006269DD /* PagingCell.swift */,
957F14081E35583500E562F8 /* PagingCellLayoutAttributes.swift */,
3E4090AA1C88BDD100800E22 /* PagingCollectionViewLayout.swift */,
95591F22222C522800677B4B /* PagingController.swift */,
95E4BA711FF15EFE008871A3 /* PagingFiniteDataSource.swift */,
9546B2AA2A1D2F06000390C6 /* PagingHostingIndicatorView.swift */,
3E4090AC1C88BDD100800E22 /* PagingIndicatorLayoutAttributes.swift */,
3E49C7221C8F5C13006269DD /* PagingIndicatorView.swift */,
954842581F42438E0072038C /* PagingInvalidationContext.swift */,
Expand All @@ -472,9 +482,6 @@
3E562ACD1CE7CD8C007623B3 /* PagingTitleCell.swift */,
3E49C7231C8F5C13006269DD /* PagingView.swift */,
3E49C7241C8F5C13006269DD /* PagingViewController.swift */,
955453C32413C80F00923BC8 /* PageViewController.swift */,
95FEEA4E2423F213009B5B64 /* PageViewManager.swift */,
956FFFCF29BE23F900477E94 /* PageViewCoordinator.swift */,
);
path = Classes;
sourceTree = "<group>";
Expand Down Expand Up @@ -582,6 +589,7 @@
3E4189201C9573FA001E0284 /* PagingViewControllerInfiniteDataSource.swift */,
95D7900F2299CE6100E6EE7C /* PagingViewControllerSizeDelegate.swift */,
95868C33200412DE004B392B /* Tween.swift */,
9546B2AE2A1D4767000390C6 /* PagingIndicatorStyle.swift */,
);
path = Protocols;
sourceTree = "<group>";
Expand Down Expand Up @@ -764,12 +772,13 @@
95D2AE5F242BCC9900AC3D46 /* Info.plist */,
95D2AE51242BCC9500AC3D46 /* ExampleApp.swift */,
95D2AE55242BCC9500AC3D46 /* DefaultView.swift */,
956F000929CCFC6C00477E94 /* InterpolatedView.swift */,
956FFFCB29BE1FD100477E94 /* ChangeItemsView.swift */,
956FFFD129BE273B00477E94 /* CustomizedView.swift */,
95F83D822623804F003B728F /* DynamicItemsView.swift */,
956F000929CCFC6C00477E94 /* InterpolatedView.swift */,
95F83D6B26237DA4003B728F /* LifecycleView.swift */,
95F83D6326237D2B003B728F /* SelectedIndexView.swift */,
95F83D822623804F003B728F /* DynamicItemsView.swift */,
956FFFCB29BE1FD100477E94 /* ChangeItemsView.swift */,
9546B2AC2A1D44EF000390C6 /* CustomIndicatorView.swift */,
95D2AE57242BCC9900AC3D46 /* Assets.xcassets */,
95D2AE5C242BCC9900AC3D46 /* LaunchScreen.storyboard */,
95D2AE59242BCC9900AC3D46 /* Preview Content */,
Expand Down Expand Up @@ -1110,7 +1119,9 @@
3E49C7281C8F5C13006269DD /* PagingIndicatorView.swift in Sources */,
95D2AE36242BB22F00AC3D46 /* PageViewDirection.swift in Sources */,
95B301171E59FCD500B95D02 /* UIEdgeInsets.swift in Sources */,
9546B2AB2A1D2F06000390C6 /* PagingHostingIndicatorView.swift in Sources */,
955444C01FC9CCFF001EC26B /* PagingMenuHorizontalAlignment.swift in Sources */,
9546B2AF2A1D4767000390C6 /* PagingIndicatorStyle.swift in Sources */,
3E49C7261C8F5C13006269DD /* PagingCell.swift in Sources */,
95D790162299D56300E6EE7C /* PagingMenuDataSource.swift in Sources */,
956FFFC429BD6E6400477E94 /* PageItemBuilder.swift in Sources */,
Expand Down Expand Up @@ -1182,6 +1193,7 @@
956FFFCC29BE1FD100477E94 /* ChangeItemsView.swift in Sources */,
95F83D6426237D2B003B728F /* SelectedIndexView.swift in Sources */,
95D2AE56242BCC9500AC3D46 /* DefaultView.swift in Sources */,
9546B2AD2A1D44EF000390C6 /* CustomIndicatorView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
63 changes: 63 additions & 0 deletions Parchment/Classes/PagingHostingIndicatorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import UIKit
import SwiftUI

/// A custom `UICollectionViewReusableView` subclass used to display a
/// view that indicates the currently selected cell. You can subclass
/// this type if you need further customization; just override the
/// `indicatorClass` property in `PagingViewController`.
@available(iOS 14.0, *)
final class PagingHostingIndicatorView: PagingIndicatorView {
private let hostingController: UIHostingController<PagingIndicator>

override init(frame: CGRect) {
let configuration = PagingIndicatorConfiguration(backgroundColor: .clear)
let rootView = PagingIndicator(configuration: configuration)
self.hostingController = UIHostingController(rootView: rootView)

super.init(frame: frame)

hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostingController.view.backgroundColor = .clear
hostingController.view.clipsToBounds = false
clipsToBounds = false
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func didMoveToWindow() {
super.didMoveToWindow()
if window == nil {
hostingController.willMove(toParent: nil)
hostingController.removeFromParent()
hostingController.didMove(toParent: nil)
} else if let parent = parentViewController() {
hostingController.willMove(toParent: parent)
parent.addChild(hostingController)
addSubview(hostingController.view)
hostingController.didMove(toParent: parent)
}
}

public override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
if let attributes = layoutAttributes as? PagingIndicatorLayoutAttributes {
let configuration = PagingIndicatorConfiguration(
backgroundColor: Color(attributes.backgroundColor ?? .clear)
)
hostingController.rootView = PagingIndicator(configuration: configuration)
hostingController.view.frame = bounds
}
}

private func parentViewController() -> UIViewController? {
var responder: UIResponder? = self
while let nextResponder = responder?.next {
if let viewController = nextResponder as? UIViewController {
return viewController
}
responder = nextResponder
}
return nil
}
}
53 changes: 53 additions & 0 deletions Parchment/Protocols/PagingIndicatorStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation
import SwiftUI

@available(iOS 14.0, *)
public protocol PagingIndicatorStyle {
associatedtype Body: View
typealias Configuration = PagingIndicatorConfiguration
@ViewBuilder func makeBody(configuration: Configuration) -> Body
}

@available(iOS 14.0, *)
struct PagingIndicator: View {
let configuration: PagingIndicatorConfiguration

@Environment(\.indicatorStyle) var style

var body: some View {
AnyView(style.makeBody(configuration: configuration))
}
}

@available(iOS 14.0, *)
public struct PagingIndicatorConfiguration {
public let backgroundColor: Color
}

@available(iOS 14.0, *)
struct DefaultPagingIndicatorStyle: PagingIndicatorStyle {
func makeBody(configuration: Configuration) -> some View {
Rectangle()
.fill(configuration.backgroundColor)
}
}

@available(iOS 14.0, *)
struct PagingIndicatorStyleKey: EnvironmentKey {
static var defaultValue: any PagingIndicatorStyle = DefaultPagingIndicatorStyle()
}

@available(iOS 14.0, *)
extension EnvironmentValues {
var indicatorStyle: any PagingIndicatorStyle {
get { self[PagingIndicatorStyleKey.self] }
set { self[PagingIndicatorStyleKey.self] = newValue }
}
}

@available(iOS 14.0, *)
extension View {
public func indicatorStyle(_ style: some PagingIndicatorStyle) -> some View {
environment(\.indicatorStyle, style)
}
}
2 changes: 2 additions & 0 deletions Parchment/Structs/PagingControllerRepresentableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ struct PagingControllerRepresentableView: UIViewControllerRepresentable {
let pagingViewController = PagingViewController(options: options)
pagingViewController.dataSource = context.coordinator
pagingViewController.delegate = context.coordinator
pagingViewController.indicatorClass = PagingHostingIndicatorView.self
pagingViewController.collectionView.clipsToBounds = false

if let items = items as? [PageItem] {
for item in items {
Expand Down

0 comments on commit 9efa304

Please sign in to comment.