Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import KsApi
import Library
import PassKit
import Prelude
import SwiftUI
import UIKit

protocol ProjectPageNavigationBarViewDelegate: AnyObject {
Expand All @@ -26,17 +27,24 @@ final class ProjectPageNavigationBarView: UIView {

private lazy var navigationShareButton: UIButton = { UIButton(type: .custom) }()

private lazy var navigationCloseButton: UIButton = {
let buttonView = UIButton(type: .custom)
|> UIButton.lens.title(for: .normal) .~ nil
|> UIButton.lens.image(for: .normal) .~ image(named: "icon--cross")
|> UIButton.lens.tintColor .~ LegacyColors.ksr_support_700.uiColor()
|> UIButton.lens.accessibilityLabel %~ { _ in Strings.accessibility_projects_buttons_close() }
|> UIButton.lens.accessibilityHint %~ { _ in Strings.Closes_project() }

return buttonView
private lazy var closeButtonHostingController: UIHostingController<CloseButtonView> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This view is very small. Because it is a small component of a UIKit view, it should be written in UIKit.

let closeButtonView = CloseButtonView { [weak self] in
self?.closeButtonTapped()
}
let hostingController = UIHostingController(rootView: closeButtonView)
hostingController.view.backgroundColor = UIColor.clear
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
return hostingController
}()

private var navigationCloseButton: UIView {
guard let view = self.closeButtonHostingController.view else {
assertionFailure("UIHostingController view should always exist")
return UIView()
}
return view
}

private lazy var navigationSaveButton: UIButton = { UIButton(type: .custom) }()

private lazy var rootStackView: UIStackView = {
Expand Down Expand Up @@ -149,7 +157,13 @@ final class ProjectPageNavigationBarView: UIView {
private func setupConstraints() {
_ = (self.rootStackView, self)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

NSLayoutConstraint.activate([
self.rootStackView.topAnchor.constraint(equalTo: self.topAnchor),
self.rootStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
self.rootStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.rootStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])

_ = (
[
Expand All @@ -169,7 +183,9 @@ final class ProjectPageNavigationBarView: UIView {
self.navigationSaveButton.widthAnchor
.constraint(equalTo: self.navigationSaveButton.heightAnchor),
self.navigationCloseButton.widthAnchor
.constraint(equalTo: self.navigationCloseButton.heightAnchor)
.constraint(equalToConstant: 44),
self.navigationCloseButton.heightAnchor
.constraint(equalToConstant: 44)
])
}

Expand All @@ -192,11 +208,6 @@ final class ProjectPageNavigationBarView: UIView {
}

private func configureSubviews() {
self.addTargetAction(
buttonItem: self.navigationCloseButton,
targetAction: #selector(ProjectPageNavigationBarView.closeButtonTapped),
event: .touchUpInside
)
self.addTargetAction(
buttonItem: self.navigationShareButton,
targetAction: #selector(ProjectPageNavigationBarView.shareButtonTapped),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions Kickstarter-iOS/SharedViews/SwiftUIHelpers/CloseButtonView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import KDS
import Library
import SwiftUI

/// A SwiftUI close button with liquid glass effect.
///
/// This view is designed to be used within UIKit views via UIHostingController.
struct CloseButtonView: View {
let onClose: () -> Void

var body: some View {
Button(action: self.onClose) {
Image(uiImage: image(named: "icon--cross") ?? UIImage())
.renderingMode(.template)
.foregroundColor(Color(LegacyColors.ksr_support_700.uiColor()))
.frame(width: 20, height: 20)
}
.frame(width: 44, height: 44)
.contentShape(Circle())
.glassedEffect(in: Circle(), interactive: true)
.accessibilityLabel(Strings.accessibility_projects_buttons_close())
.accessibilityHint(Strings.Closes_project())
}
}
42 changes: 42 additions & 0 deletions Kickstarter-iOS/SharedViews/SwiftUIHelpers/GlassEffect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import SwiftUI

extension View {
/// Applies a glass/liquid glass effect to a view within a shape.
/// - Parameters:
/// - shape: The shape to apply the glass effect within
/// - interactive: Whether the effect should be interactive (iOS 26+)
/// - Returns: A view with the glass effect applied
@ViewBuilder
func glassedEffect(in shape: some Shape, interactive: Bool = false) -> some View {
if #available(iOS 26.0, *) {
self.glassEffect(interactive ? .regular.interactive() : .regular, in: shape)
} else {
self.background {
shape.glassed()
}
}
}
}

extension Shape {
/// Creates a glass-like effect fallback for iOS < 26
fileprivate func glassed() -> some View {
self
.fill(.ultraThinMaterial)
.fill(
.linearGradient(
colors: [
.primary.opacity(0.08),
.primary.opacity(0.05),
.primary.opacity(0.01),
.clear,
.clear,
.clear
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.stroke(.primary.opacity(0.2), lineWidth: 0.7)
}
}
8 changes: 8 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,8 @@
AAEABEA42DA8BDCD00B09688 /* ProjectWebURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEABEA32DA8BDCA00B09688 /* ProjectWebURL.swift */; };
AAEABEA62DA8BFA400B09688 /* ProjectPamphletMainCellProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEABEA52DA8BF9F00B09688 /* ProjectPamphletMainCellProperties.swift */; };
AAEABEA82DA8C03D00B09688 /* VideoViewProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEABEA72DA8C03B00B09688 /* VideoViewProperties.swift */; };
B807448E2F0F2A9E00D9C667 /* CloseButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B807448C2F0F2A9E00D9C667 /* CloseButtonView.swift */; };
B807448F2F0F2A9E00D9C667 /* GlassEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B807448D2F0F2A9E00D9C667 /* GlassEffect.swift */; };
D002CAE1218CF8F1009783F2 /* WatchProjectMutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002CAE0218CF8F1009783F2 /* WatchProjectMutation.swift */; };
D002CAE3218CF91D009783F2 /* WatchProjectInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002CAE2218CF91D009783F2 /* WatchProjectInput.swift */; };
D002CAE5218CF951009783F2 /* WatchProjectResponseEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002CAE4218CF951009783F2 /* WatchProjectResponseEnvelope.swift */; };
Expand Down Expand Up @@ -2999,6 +3001,8 @@
AAEABEA32DA8BDCA00B09688 /* ProjectWebURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectWebURL.swift; sourceTree = "<group>"; };
AAEABEA52DA8BF9F00B09688 /* ProjectPamphletMainCellProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectPamphletMainCellProperties.swift; sourceTree = "<group>"; };
AAEABEA72DA8C03B00B09688 /* VideoViewProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoViewProperties.swift; sourceTree = "<group>"; };
B807448C2F0F2A9E00D9C667 /* CloseButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseButtonView.swift; sourceTree = "<group>"; };
B807448D2F0F2A9E00D9C667 /* GlassEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassEffect.swift; sourceTree = "<group>"; };
D002CAE0218CF8F1009783F2 /* WatchProjectMutation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchProjectMutation.swift; sourceTree = "<group>"; };
D002CAE2218CF91D009783F2 /* WatchProjectInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchProjectInput.swift; sourceTree = "<group>"; };
D002CAE4218CF951009783F2 /* WatchProjectResponseEnvelope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchProjectResponseEnvelope.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5881,6 +5885,8 @@
19C8E56729A923F4007C3504 /* SwiftUIHelpers */ = {
isa = PBXGroup;
children = (
B807448C2F0F2A9E00D9C667 /* CloseButtonView.swift */,
B807448D2F0F2A9E00D9C667 /* GlassEffect.swift */,
19C8E56829A9249D007C3504 /* TextInputFieldModifiers.swift */,
);
path = SwiftUIHelpers;
Expand Down Expand Up @@ -9307,6 +9313,8 @@
19C8E56929A9249D007C3504 /* TextInputFieldModifiers.swift in Sources */,
D6B6766720FF8D3C0082717D /* SettingsNewslettersViewController.swift in Sources */,
77E6440320F65074005F6B38 /* HelpViewController.swift in Sources */,
B807448E2F0F2A9E00D9C667 /* CloseButtonView.swift in Sources */,
B807448F2F0F2A9E00D9C667 /* GlassEffect.swift in Sources */,
8ACD29F7243E48260044BC17 /* PledgePaymentMethodsDataSource.swift in Sources */,
8A13D15F249559CB007E2C0B /* PledgeExpandableRewardsHeaderViewController.swift in Sources */,
77FA6CA920F3E18F00809E31 /* SettingsTableViewCell.swift in Sources */,
Expand Down