Skip to content
Merged
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 @@ -65,6 +65,7 @@ struct ModalPreviewHelpers {
Text("Warning Background").tag(UniversalColor.warningBackground)
Text("Danger Background").tag(UniversalColor.dangerBackground)
}
BackgroundStylePicker(selection: self.$model.backgroundStyle)
BorderWidthPicker(selection: self.$model.borderWidth)
Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap)
.disabled(self.footer == nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ struct AutocapitalizationPicker: View {
}
}

// MARK: - BackgroundStylePicker

struct BackgroundStylePicker: View {
@Binding var selection: ComponentsKit.BackgroundStyle

var body: some View {
Picker("Background Style", selection: self.$selection) {
Text("Solid").tag(ComponentsKit.BackgroundStyle.solid)
Text("Blur").tag(ComponentsKit.BackgroundStyle.blur)
if #available(iOS 26.0, *) {
Text("Liquid Glass").tag(ComponentsKit.BackgroundStyle.liquidGlass)
}
}
}
}

// MARK: - BorderWidthPicker

struct BorderWidthPicker: View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ struct AlertPreview: View {
Text("Warning Background").tag(UniversalColor.warningBackground)
Text("Danger Background").tag(UniversalColor.dangerBackground)
}
BackgroundStylePicker(selection: self.$model.backgroundStyle)
BorderWidthPicker(selection: self.$model.borderWidth)
Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap)
Picker("Content Paddings", selection: self.$model.contentPaddings) {
Expand Down
4 changes: 4 additions & 0 deletions Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public struct AlertVM: ComponentVM {
/// The background color of the alert.
public var backgroundColor: UniversalColor?

/// Defines how the alert renders its background.
public var backgroundStyle: BackgroundStyle = .solid

/// The border thickness of the alert.
///
/// Defaults to `.small`.
Expand Down Expand Up @@ -61,6 +64,7 @@ extension AlertVM {
var modalVM: CenterModalVM {
return CenterModalVM {
$0.backgroundColor = self.backgroundColor
$0.backgroundStyle = self.backgroundStyle
$0.borderWidth = self.borderWidth
$0.closesOnOverlayTap = self.closesOnOverlayTap
$0.contentPaddings = self.contentPaddings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public struct BottomModalVM: ModalVM {
/// The background color of the modal.
public var backgroundColor: UniversalColor?

/// Defines how modal renders its background.
public var backgroundStyle: BackgroundStyle = .solid

/// The border thickness of the modal.
///
/// Defaults to `.small`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public struct CenterModalVM: ModalVM {
/// The background color of the modal.
public var backgroundColor: UniversalColor?

/// Defines how modal renders its background.
public var backgroundStyle: BackgroundStyle = .solid

/// The border thickness of the modal.
///
/// Defaults to `.small`.
Expand Down
18 changes: 13 additions & 5 deletions Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public protocol ModalVM: ComponentVM {
/// The background color of the modal.
var backgroundColor: UniversalColor? { get set }

/// Defines how modal renders its background.
var backgroundStyle: BackgroundStyle { get set }

/// The border thickness of the modal.
var borderWidth: BorderWidth { get set }

Expand Down Expand Up @@ -36,10 +39,15 @@ public protocol ModalVM: ComponentVM {
// MARK: - Helpers

extension ModalVM {
var preferredBackgroundColor: UniversalColor {
return self.backgroundColor ?? .themed(
light: UniversalColor.background.light,
dark: UniversalColor.secondaryBackground.dark
)
var preferredBackgroundColor: UniversalColor? {
switch self.backgroundStyle {
case .solid:
return self.backgroundColor ?? .themed(
light: UniversalColor.background.light,
dark: UniversalColor.secondaryBackground.dark
)
case .liquidGlass, .blur:
return self.backgroundColor
}
}
}
48 changes: 43 additions & 5 deletions Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ struct ModalContent<VM: ModalVM, Header: View, Body: View, Footer: View>: View {
.padding(.bottom, self.model.contentPaddings.bottom)
}
.frame(maxWidth: self.model.size.maxWidth, alignment: .leading)
.background(self.model.preferredBackgroundColor.color)
.clipShape(RoundedRectangle(cornerRadius: self.model.cornerRadius.value))
.overlay(
RoundedRectangle(cornerRadius: self.model.cornerRadius.value)
.strokeBorder(UniversalColor.divider.color, lineWidth: self.model.borderWidth.value)
.modalBackground(
shape: RoundedRectangle(cornerRadius: model.cornerRadius.value),
model: self.model
)
.padding(self.model.outerPaddings.edgeInsets)
}
Expand All @@ -74,3 +72,43 @@ struct ModalContent<VM: ModalVM, Header: View, Body: View, Footer: View>: View {
return self.bodySize.height + self.bodyTopPadding + self.bodyBottomPadding
}
}

extension View {
@ViewBuilder
fileprivate func modalBackground<BackgroundShape: InsettableShape>(
shape: BackgroundShape,
model: any ModalVM
) -> some View {
switch model.backgroundStyle {
case .solid:
self.background(model.preferredBackgroundColor?.color)
.clipShape(shape)
.overlay(
shape
.strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value)
)
case .blur:
self
.background {
shape
.fill(.thinMaterial)
.overlay {
shape.strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value)
}
}
.background(model.preferredBackgroundColor?.color)
.clipShape(shape)
case .liquidGlass:
if #available(iOS 26.0, *) {
self.glassEffect(
.regular
.tint(model.preferredBackgroundColor?.color)
.interactive(),
in: shape
)
} else {
self
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct ModalOverlay<VM: ModalVM>: View {
Group {
switch self.model.overlayStyle {
case .dimmed:
Color.black.opacity(0.7)
Color.black.opacity(0.35)
case .blurred:
Color.clear.background(.ultraThinMaterial)
case .transparent:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ open class UKModalController<VM: ModalVM>: UIViewController {
public var footer: UIView?
/// The content view, holding the header, body, and footer.
public let contentView = UIView()
/// The visual effect container used to render blur and liquid glass modal backgrounds.
public let backgroundEffectView = UIVisualEffectView()
/// A scrollable wrapper for the body content.
public let bodyWrapper: UIScrollView = ContentSizedScrollView()
/// The overlay view that appears behind the modal.
Expand Down Expand Up @@ -76,12 +78,13 @@ open class UKModalController<VM: ModalVM>: UIViewController {
open func setup() {
self.view.addSubview(self.overlay)
self.view.addSubview(self.contentView)
self.contentView.addSubview(self.backgroundEffectView)
if let header {
self.contentView.addSubview(header)
self.backgroundEffectView.contentView.addSubview(header)
}
self.contentView.addSubview(self.bodyWrapper)
self.backgroundEffectView.contentView.addSubview(self.bodyWrapper)
if let footer {
self.contentView.addSubview(footer)
self.backgroundEffectView.contentView.addSubview(footer)
}

self.bodyWrapper.addSubview(self.body)
Expand Down Expand Up @@ -141,6 +144,7 @@ open class UKModalController<VM: ModalVM>: UIViewController {
open func style() {
Self.Style.overlay(self.overlay, model: self.model)
Self.Style.contentView(self.contentView, model: self.model)
Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model)
Self.Style.bodyWrapper(self.bodyWrapper)
}

Expand All @@ -149,6 +153,7 @@ open class UKModalController<VM: ModalVM>: UIViewController {
/// Configures the layout of the modal's subviews.
open func layout() {
self.overlay.allEdges()
self.backgroundEffectView.allEdges()

if let header {
header.top(self.model.contentPaddings.top)
Expand Down Expand Up @@ -242,6 +247,7 @@ open class UKModalController<VM: ModalVM>: UIViewController {

@objc private func handleTraitChanges() {
Self.Style.contentView(self.contentView, model: self.model)
Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model)
}
}

Expand All @@ -252,18 +258,39 @@ extension UKModalController {
static func overlay(_ view: UIView, model: VM) {
switch model.overlayStyle {
case .dimmed:
view.backgroundColor = .black.withAlphaComponent(0.7)
view.backgroundColor = .black.withAlphaComponent(0.35)
case .transparent:
view.backgroundColor = .clear
case .blurred:
(view as? UIVisualEffectView)?.effect = UIBlurEffect(style: .systemUltraThinMaterial)
}
}
static func contentView(_ view: UIView, model: VM) {
view.backgroundColor = model.preferredBackgroundColor.uiColor
view.layer.cornerRadius = model.cornerRadius.value
}
static func backgroundEffectView(_ view: UIVisualEffectView, model: VM) {
view.layer.cornerRadius = model.cornerRadius.value
view.layer.borderColor = UniversalColor.divider.cgColor
view.layer.borderWidth = model.borderWidth.value
view.clipsToBounds = true

switch model.backgroundStyle {
case .solid:
view.effect = nil
view.backgroundColor = model.preferredBackgroundColor?.uiColor
case .blur:
view.effect = UIBlurEffect(style: .systemThinMaterial)
view.backgroundColor = model.preferredBackgroundColor?.uiColor
case .liquidGlass:
if #available(iOS 26.0, *) {
let effect = UIGlassEffect(style: .regular)
effect.tintColor = model.preferredBackgroundColor?.uiColor
effect.isInteractive = true
view.effect = effect
} else {
view.effect = nil
}
}
}
static func bodyWrapper(_ scrollView: UIScrollView) {
scrollView.delaysContentTouches = false
Expand Down
11 changes: 11 additions & 0 deletions Sources/ComponentsKit/Shared/Types/BackgroundStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

/// Defines how a component renders its background.
public enum BackgroundStyle {
/// A regular filled background using the component's configured background color.
case solid
/// A system liquid glass effect that lets underlying content show through the component.
@available(iOS 26.0, *) case liquidGlass
/// A system blur material that softens content behind the component.
case blur
}