Skip to content

Commit

Permalink
Added modal sheet view and fixed existing objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanvorobei committed Nov 24, 2024
1 parent 37530ca commit 32acaaa
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 3 deletions.
21 changes: 21 additions & 0 deletions Sources/SwiftUIExtension/Compability/TransitionBlurReplace.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import SwiftUI

extension View {

public func transitionBlurReplaceCombability() -> some View {
modifier(TransitionBlurReplace())
}
}

struct TransitionBlurReplace: ViewModifier {

func body(content: Content) -> some View {
if #available(iOS 18.0, *) {
content
.transition(.blurReplace.combined(with: .opacity))
} else {
content
.transition(.opacity)
}
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftUIExtension/Extensions/ImageExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SwiftUI
extension Image {

public static func load(url: URL, completion: @escaping (Image?) -> Void) {
DispatchQueue.global(qos: .background).async {
DispatchQueue.global(qos: .default).async {
if let data = try? Data(contentsOf: url) {

#if canImport(UIKit)
Expand Down
64 changes: 64 additions & 0 deletions Sources/SwiftUIExtension/Views/Mimicrate/LargeButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import SwiftUI

extension ButtonStyle where Self == LargeButtonStyle {

public static func large(_ colorise: ButtonColorise) -> Self {
return LargeButtonStyle(colorise)
}
}

public struct LargeButtonStyle: ButtonStyle {

@Environment(\.isEnabled) private var isEnabled: Bool

let colorise: ButtonColorise

init(_ colorise: ButtonColorise) {
self.colorise = colorise
}

public func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.foregroundColor(colorise.foregroundColor)
.frame(maxWidth: .infinity)
.padding(.vertical, 15)
.padding(.horizontal, 10)
.background {
colorise.backgroundColor
.clipShape(.rect(cornerRadius: 12))
}
.opacity(configuration.isPressed ? 0.7 : 1)
.animation(.default, value: configuration.isPressed)
.opacity(isEnabled ? 1 : 0.4)
.animation(.default, value: isEnabled)
}
}

public enum ButtonColorise {

case colorful
case tinted
case grayed

var foregroundColor: Color {
switch self {
case .colorful:
return .white
case .tinted:
return .accentColor
case .grayed:
return .accentColor
}
}
var backgroundColor: Color {
switch self {
case .colorful:
return .accentColor
case .tinted:
return .accentColor.opacity(0.12)
case .grayed:
return .gray.opacity(0.15)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import SwiftUI

struct MimicrateCloseButton: View {

let action: () -> Void

var body: some View {
Button {
action()
} label: {
Image(systemName: "xmark")
.font(.footnote.bold())
.foregroundColor(.gray)
.padding(7)
.background {
Circle()
}
.tint(.gray.opacity(0.2))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftUI

extension View {

public func modalSheet<Content, Selection: Hashable>(
isPresented: Binding<Bool>,
selection: Binding<Selection>,
dismissable: Bool,
onDismiss: (() -> Void)? = nil,
@ViewBuilder content: @escaping () -> Content
) -> some View where Content : View {

let sheet = ModalSheetModifier(
isPresented: isPresented,
selection: selection,
dismissable: dismissable,
onDismiss: onDismiss,
modalContent: content
)

return self.modifier(sheet)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import SwiftUI

/**
Idea have to reusable basic style of modal sheet content with title+descrition & action button.
*/
public struct ModalSheetContentView<Content: View, ActionContent: View>: View {

let title: String
let description: String?
let content: () -> Content
let actionContent: () -> ActionContent
let actionEnabled: Bool
let action: () -> Void
let dismissable: Bool

public init(
title: String,
description: String?,
@ViewBuilder content: @escaping () -> Content,
@ViewBuilder actionContent: @escaping () -> ActionContent,
actionEnabled: Bool,
action: @escaping () -> Void,
dismissable: Bool
) {
self.title = title
self.description = description
self.content = content
self.actionContent = actionContent
self.actionEnabled = actionEnabled
self.action = action

self.dismissable = dismissable
}

public var body: some View {
VStack(alignment: .center, spacing: .zero) {

VStack(alignment: .center) {
Text(title)
.font(.title.weight(.bold))

if let description {
Text(description)
}
}

FixedSpacer(height: Spaces.default_double)
content()
FixedSpacer(height: Spaces.default_double)

Button {
action()
} label: {
actionContent()
}
.buttonStyle(.large(.tinted))
.disabled(!actionEnabled)
}
.multilineTextAlignment(.center)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import SwiftUI
import SwiftBoost

struct ModalSheetModifier<ModalContent: View, Selection: Hashable>: ViewModifier {

// States
@Binding private var isPresented: Bool
@Binding private var selection: Selection
private let dismissable: Bool
private let onDismiss: (() -> Void)?

// Content
private let modalContent: () -> ModalContent

// Private
@State private var dragOffset: CGSize = .zero

init(isPresented: Binding<Bool>, selection: Binding<Selection>, dismissable: Bool, onDismiss: (() -> Void)?, @ViewBuilder modalContent: @escaping () -> ModalContent) {
self._isPresented = isPresented
self._selection = selection
self.dismissable = dismissable
self.onDismiss = onDismiss
self.modalContent = modalContent
}

func body(content: Content) -> some View {
ZStack {

content
.zIndex(0)

if isPresented {

// Background
Color.black.opacity(0.4)
.ignoresSafeArea()
.transition(.opacity)
.animation(.easeOut, value: isPresented)
.zIndex(1)

// Content
VStack {
Spacer()
VStack {
modalContent()
.transitionBlurReplaceCombability()
.padding(.top, Spaces.default_half + Spaces.default_more)
.padding(.bottom, Spaces.default_more)
.padding(.horizontal, Spaces.default_double)
.overlay {
if dismissable {
VStack {
MimicrateCloseButton {
isPresented = false
}
Spacer()
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(Spaces.default_more)
}
}
}
.background {
Color.white
}
.clipShape(.rect(cornerRadius: cornerRadius))
.shadow(color: .black.opacity(0.12), radius: 6, x: .zero, y: 6)
.shadow(color: .black.opacity(0.15), radius: 16, x: .zero, y: 12)
.overlay {
RoundedRectangle(cornerRadius: cornerRadius)
.strokeBorder(.secondary.opacity(0.5), lineWidth: 1)
}
.padding(.horizontal, padding)
.padding(.bottom, padding)
.offset(y: calculateDragOffset)
.gesture(
DragGesture()
.onChanged { gesture in
dragOffset = gesture.translation
}
.onEnded { _ in
if dragOffset.height > 100 && dismissable {
isPresented = false
} else {
withAnimation(.interpolatingSpring(duration: 0.26)) {
dragOffset = .zero
}
}
}
)
}
.ignoresSafeArea()
.transition(.move(edge: .bottom))
.zIndex(2)
}
}
.animation(.smooth(duration: presentDimissDuration), value: isPresented)
.animation(.default, value: selection)
.onChange(of: isPresented) { isPresented in
if !isPresented {
delay(presentDimissDuration) {
self.onDismiss?()
}
}
}
}

// MARK: - Private

private var calculateDragOffset: CGFloat {
let dragDistance = dragOffset.height
let calm = dragDistance < 0 || !dismissable

if calm {
let squaredDistance = sqrt(abs(dragDistance))
if dragDistance < 0 {
return max(-squaredDistance * 3, dragDistance)
} else {
return min(squaredDistance * 2, dragDistance)
}
} else {
return dragDistance
}
}

// MARK: - Constants

private var padding: CGFloat = 10
private var cornerRadius: CGFloat { UIScreen.main.displayCornerRadius - padding }
private var presentDimissDuration: TimeInterval { 0.41 }
}
3 changes: 1 addition & 2 deletions Sources/SwiftUIExtension/Views/Mimicrate/NativeSection.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import SwiftUI
import SafeSFSymbols
import SwiftUIIntrospect

public struct NativeSection<Content: View, Detail: View>: View {
Expand Down Expand Up @@ -37,7 +36,7 @@ public struct NativeSection<Content: View, Detail: View>: View {
.foregroundColor(.primary)
.font(.title2)
.fontWeightCompability(.bold)
Image(.chevron.right)
Image("chevron.right")
.foregroundColor(.secondary)
.font(.footnote)
.fontWeightCompability(.heavy)
Expand Down

0 comments on commit 32acaaa

Please sign in to comment.