Skip to content

Commit

Permalink
Merge pull request #27 from joogps/revamp
Browse files Browse the repository at this point in the history
Revamp
  • Loading branch information
joogps committed Mar 20, 2024
2 parents 834f2c9 + 6f729e2 commit 2569a9d
Show file tree
Hide file tree
Showing 17 changed files with 568 additions and 461 deletions.
182 changes: 81 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1> SlideOverCard
<img align="right" alt="Project logo" src="../assets/icon-small.png" width=74px>
<img align="right" alt="Project logo" src="../assets/icon-small.png" width=128px>
</h1>

<p>
Expand All @@ -10,123 +10,103 @@
</a>
</p>

A SwiftUI card design, similar to the one used by Apple in HomeKit, AirPods, Apple Card and AirTag setup, NFC scanning, Wi-Fi password sharing and more. It is specially great for setup interactions.
A SwiftUI card design similar to the one used by Apple in HomeKit, AirPods, Apple Card and AirTag setup, NFC scanning, Wi-Fi password sharing and more. It is specially great for setup interactions.

<br>
<p>
<img alt="Clear Spaces demo" src="../assets/demo-clear-spaces.gif" height=400px>
<img alt="QR code scanner demo" src="../assets/demo-qr-code.gif" height=400px>
<img alt="Example preview demo" src="../assets/demo-example.gif" height=400px>
<img alt="Clear Spaces demo" src="../assets/demo-clear-spaces.gif" margin-right=20px>
<img alt="QR code scanner demo" src="../assets/demo-qr-code.gif">
<img alt="Example preview demo" src="../assets/demo-example.gif">
</p>
<br>

_From left to right: SlideOverCard being used in [Clear Spaces](https://apps.apple.com/us/app/clear-spaces/id1532666619), a QR code scanner prompt (made with [CodeScanner](https://github.com/twostraws/CodeScanner)) and a demo of the project's Xcode preview_
> _From left to right: SlideOverCard being used in [Clear Spaces](https://apps.apple.com/us/app/clear-spaces/id1532666619), a QR code scanner prompt (made with [CodeScanner](https://github.com/twostraws/CodeScanner)) and a sample demo app_
## Installation
This repository is a Swift package, so all you gotta do is paste the repository link and include it in your project under **File > Add packages**. Then, just add `import SlideOverCard` to the files where this package will be referenced and you're good to go!
This repository is a Swift package, so just include it in your Xcode project and target under **File > Add package dependencies**. Then, `import SlideOverCard` to the Swift files where you'll be using it.

If your app runs on iOS 13, you might find a problem with keyboard responsiveness in your layout. That's caused by a SwiftUI limitation, unfortunately, since the [`ignoresSafeArea`](https://developer.apple.com/documentation/swiftui/text/ignoressafearea(_:edges:)) modifier was only introduced for the SwiftUI framework in the iOS 14 update.
> [!NOTE]
> If your app runs on iOS 13, you might find a problem with keyboard responsiveness in your layout. That's caused by a SwiftUI limitation, unfortunately, since the [`ignoresSafeArea`](https://developer.apple.com/documentation/swiftui/text/ignoressafearea(_:edges:)) modifier was only introduced for the SwiftUI framework in the iOS 14 update.
>
## Usage
You can add a card to your app in two different ways. The first one is by adding a `.slideOverCard()` modifier, which works similarly to a `.sheet()`:
Adding a card to your app is insanely easy. Just add a `.slideOverCard()` modifier anywhere in your view hierarchy, similarly to a `.sheet()`:
```swift
.slideOverCard(isPresented: $isPresented) {
// Here goes your awesome content
}
```

Here, `$isPresented` is a boolean binding. This way you can dismiss the view anytime by setting it to `false`.

## Customization

This view will have a transition, drag controls and a dismiss button set by default. You can override this by setting the `dragEnabled`, `dragToDismiss` and `displayExitButton` boolean parameters:
```swift

// This creates a card that can be dragged, but not dismissed by dragging
.slideOverCard(isPresented: $isPresented, options: [.disableDragToDismiss]) {
}

// This creates a card that can't be dragged or dismissed by dragging
.slideOverCard(isPresented: $isPresented, options: [.disableDrag, .disableDragToDismiss]) {
}

// This creates a card with no exit button
.slideOverCard(isPresented: $isPresented, options: [.hideDismissButton]) {
}
```

If you want to change styling attributes of the card, such as the **corner size**, the **corner style**, the **inner and outer paddings**, the **dimming opacity** and the **shape fill style**, such as a gradient, just specify a custom `SOCStyle` struct.

```swift
.slideOverCard(isPresented: $isPresented, style: SOCStyle(corners: 24.0,
continuous: false,
innerPadding: 16.0, outerPadding: 4.0,
dimmingOpacity: 0.1,
style: .black)) {
}
```

In case you want to execute code when the view is dismissed (either by the exit button or drag controls), you can also set an optional `onDismiss` closure parameter:

And that's it! It just works. In this case, `$isPresented` is a boolean binding. This way you can dismiss the view anytime by setting it to `false`.

## Extra steps
<details>
<summary><b>Customization</b></summary><br>

The default `.slideOverCard()` modifier will have a transition, drag controls and a dismiss button set by default. You can override this by setting the `dragEnabled`, `dragToDismiss` and `displayExitButton` boolean parameters:
```swift

// This creates a card that can be dragged, but not dismissed by dragging
.slideOverCard(isPresented: $isPresented, options: [.disableDragToDismiss]) {
}

// This creates a card that can't be dragged or dismissed by dragging
.slideOverCard(isPresented: $isPresented, options: [.disableDrag, .disableDragToDismiss]) {
}

// This creates a card with no dismiss button
.slideOverCard(isPresented: $isPresented, options: [.hideDismissButton]) {
}
```

If you want to change styling attributes of the card, such as the **corner size**, the **corner style**, the **inner and outer paddings**, the **dimming opacity** and the **shape fill style**, such as a gradient, just specify a custom `SOCStyle` struct.

```swift
.slideOverCard(isPresented: $isPresented, style: SOCStyle(cornerRadius: 24.0,
continuous: false,
innerPadding: 16.0,
outerPadding: 4.0,
dimmingOpacity: 0.1,
style: .black)) {
}
```

In case you want to execute code when the view is dismissed (either by the exit button or drag controls), you can also set an optional `onDismiss` closure parameter:

```swift
// This card will print some text when dismissed
.slideOverCard(isPresented: $isPresented, onDismiss: {
print("I was dismissed.")
}) {
// Here goes your amazing layout
}
```

Alternatively, you can add the card using a binding to an optional identifiable object. That will automatically animate the card between screen changes.
```swift
// This uses a binding to an optional object in a switch statement
.slideOverCard(item: $activeCard) { item in
switch item {
case .welcomeView:
WelcomeView()
case .loginView:
LoginView()
default:
..........
}
}
```
</details>

<details>
<summary><b>Accessory views</b></summary><br>

This package also includes a few accessory views to enhance your card layout. The first one is the `SOCActionButton()` button style, which can be applied to any button to give it a default "primary action" look, based on the app's accent color. The `SOCAlternativeButton()` style will reproduce the same design, but with gray. And `SOCEmptyButton()` will create a text-only button. You can use them like this:
```swift
// This card will print some text when dismissed
.slideOverCard(isPresented: $isPresented, onDismiss: {
print("I was dismissed.")
}) {
// Here goes your amazing layout
}
```

---

Alternatively, you can add the card using a binding to an optional identifiable object. That will automatically animate the card between screen changes.
```swift
// This uses a binding to an optional object in a switch statement
.slideOverCard(item: $activeCard) { item in
switch item {
case .welcomeView:
WelcomeView()
case .loginView:
LoginView()
default:
..........
}
}
```

You can even instantiate a card by your own by adding a `SlideOverCard` view to a ZStack.
```swift
// Using the standalone view
ZStack {
Color.white

SlideOverCard(isPresented: $isPresented) {
// Here goes your super-duper cool screen
}
}
```

## Accessory views
This package also includes a few accessory views to enhance your card layout. The first one is the `SOCActionButton()` button style, which can be applied to any button to give it a default "primary action" look, based on the app's accent color. The `SOCAlternativeButton()` style will reproduce the same design, but with gray. And `SOCEmptyButton()` will create an all-text "last option" kind of button. You can use them like this:
```swift
Button("Do something", action: {
Button("Do something") {
...
}).buttonStyle(SOCActionButton())
}.buttonStyle(SOCActionButton()) // Use the modifier of your choice
```

There's also the `SOCDismissButton()` view. This view will create the default dismiss button icon used for the card (based on https://github.com/joogps/ExitButton).

## Manager
If you want to show a card as an overlay to all content in the screen, including the tab and navigation bars, you should use the `SOCManager`. The manager helps you display a card as a transparent view controller that covers the screen, therefore going past your SwiftUI content. To present this overlay, use:
```swift
SOCManager.present(isPresented: $isPresented) {
// Here goes your design masterpiece
}
```

And to dismiss, just call:
```swift
SOCManager.dismiss(isPresented: $isPresented)
```

# Example

The SwiftUI code for a demo view can be found [here](https://github.com/joogps/SlideOverCard/blob/f6cb0e2bac67555fd74cdadf3e6ca542538f0c23/Sources/SlideOverCard/SlideOverCard.swift#L128). It's an Xcode preview, and you can experience it right within the package, under **Swift Package Dependencies**, in your project.
</details>
42 changes: 20 additions & 22 deletions Sources/SlideOverCard/Accessories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import SwiftUI

/// A button style that represents a main action in a `SlideOverCard`
/// A button style that represents a main action used in a `SlideOverCard`
public struct SOCActionButton: ButtonStyle {
let textColor: Color

public init(textColor: Color = .white) {
self.textColor = textColor
}
Expand All @@ -22,22 +23,29 @@ public struct SOCActionButton: ButtonStyle {
.padding(.vertical, 20)
.foregroundColor(textColor)
Spacer()
}.background(Color.accentColor).overlay(configuration.isPressed ? Color.black.opacity(0.2) : nil).cornerRadius(12)
}
.background(Color.accentColor)
.overlay(configuration.isPressed ? Color.black.opacity(0.2) : nil)
.cornerRadius(12)
}
}

/// A button style that represents an alternative action in a `SlideOverCard`
/// A button style that represents an alternative action used in a `SlideOverCard`
public struct SOCAlternativeButton: ButtonStyle {
public init() {}
public init() {

}

public func makeBody(configuration: Configuration) -> some View {
SOCActionButton(textColor: .primary).makeBody(configuration: configuration).accentColor(Color(.systemGray5))
}
}

/// A button style with no background and tinted text in a `SlideOverCard`
/// A button style with no background and tinted text used in a `SlideOverCard`
public struct SOCEmptyButton: ButtonStyle {
public init() {}
public init() {

}

public func makeBody(configuration: Configuration) -> some View {
configuration.label
Expand All @@ -52,27 +60,17 @@ public struct SOCEmptyButton: ButtonStyle {
public struct SOCDismissButton: View {
@Environment(\.colorScheme) var colorScheme

public init() {

}

public var body: some View {
ZStack {
Circle()
.fill(Color(white: colorScheme == .dark ? 0.19 : 0.93))
.fill(Color(white: colorScheme == .dark ? 0.19 : 0.89))
Image(systemName: "xmark")
.resizable()
.scaledToFit()
.font(Font.body.weight(.bold))
.scaleEffect(0.416)
.font(.system(size: 13.0, weight: .bold))
.foregroundColor(Color(white: colorScheme == .dark ? 0.62 : 0.51))
}
}
}

struct Acessories_Previews: PreviewProvider {
static var previews: some View {
Group {
Button("Action") {}.buttonStyle(SOCActionButton())
Button("Alternative") {}.buttonStyle(SOCAlternativeButton())
Button("Empty") {}.buttonStyle(SOCEmptyButton())
SOCDismissButton()
}.previewLayout(.sizeThatFits)
}
}
77 changes: 0 additions & 77 deletions Sources/SlideOverCard/Deprecated.swift

This file was deleted.

15 changes: 15 additions & 0 deletions Sources/SlideOverCard/Helper/Animation+Default.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Animation+Default.swift
//
//
// Created by João Gabriel Pozzobon dos Santos on 19/03/24.
//

import SwiftUI

extension Animation {
/// A default spring animation for the card transitions
internal static var defaultSpring: Animation {
.spring(response: 0.35, dampingFraction: 1)
}
}
Loading

0 comments on commit 2569a9d

Please sign in to comment.