Created
April 30, 2022 03:28
-
-
Save aheze/8ce478b7eae189b76f28d6073d67b81b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// Paywall | |
// | |
// Created by A. Zheng (github.com/aheze) on 4/29/22. | |
// Copyright © 2022 A. Zheng. All rights reserved. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
@State var presentingPaywall = false | |
var body: some View { | |
Button { | |
presentingPaywall = true | |
} label: { | |
Text("Present Paywall") | |
} | |
.buttonStyle(.borderedProminent) | |
.sheet(isPresented: $presentingPaywall) { | |
PaywallView(presentingPaywall: $presentingPaywall) | |
} | |
} | |
} | |
struct PaywallView: View { | |
@Binding var presentingPaywall: Bool | |
var body: some View { | |
NavigationView { | |
ScrollView { | |
VStack(spacing: 20) { | |
RowView( | |
color: .red, | |
imageName: "menucard.fill", | |
title: "Better Results", | |
description: "Swipe through specific types of restaurants in your area!" | |
) | |
RowView( | |
color: .red.offset(by: 0.03), | |
imageName: "map.fill", | |
title: "Place Filters", | |
description: "Coffee shops, bars, brunch, ice cream & more!" | |
) | |
RowView( | |
color: .red.offset(by: 0.06), | |
imageName: "fork.knife", | |
title: "Cuisine Filters", | |
description: "Chinese, American, Japanese, Indian & More!" | |
) | |
RowView( | |
color: .red.offset(by: 0.09), | |
imageName: "heart.fill", | |
title: "Dietary Filters", | |
description: "Vegan, Gluten-Free, Vegetarian & Halal!" | |
) | |
} | |
.padding(.top, 16) | |
.padding(.horizontal, 20) | |
.padding(.bottom, 160) /// make space for the bottom blur | |
} | |
.overlay(alignment: .bottom) { | |
VStack { | |
Text("One-time purchase of $0.99!") | |
Button { | |
print("Unlock Swipe Filters") | |
} label: { | |
Text("Unlock Swipe Filters") | |
.font(.title2.bold()) | |
.foregroundColor(.white) | |
.frame(maxWidth: .infinity) | |
.padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)) | |
.background(.black) | |
.cornerRadius(20) | |
} | |
Button { | |
print("Restore Purchase") | |
} label: { | |
Text("Restore Purchase") | |
.foregroundColor(.secondary) | |
} | |
} | |
.padding(20) | |
.background( | |
VisualEffectView(.systemChromeMaterial) | |
.mask( | |
LinearGradient( | |
stops: [ | |
.init(color: .clear, location: 0), | |
.init(color: .black, location: 0.2), | |
], | |
startPoint: .top, | |
endPoint: .bottom | |
) | |
) | |
.edgesIgnoringSafeArea(.all) | |
.padding(.top, -30) /// extend out top | |
) | |
} | |
.navigationTitle("Unlock Swipe Filters") | |
.toolbar { | |
ToolbarItem(placement: .navigationBarTrailing) { | |
Button { | |
presentingPaywall = false | |
} label: { | |
Image(systemName: "xmark") | |
.font(.system(size: 13, weight: .bold)) | |
.foregroundColor(.secondary) | |
.padding(8) | |
.background { | |
Circle() | |
.fill( | |
Color(UIColor.secondarySystemBackground) | |
) | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
struct RowView: View { | |
let color: UIColor | |
let imageName: String | |
let title: String | |
let description: String | |
var body: some View { | |
HStack(spacing: 20) { | |
Color.white.opacity(0.4) | |
.frame(width: 60, height: 60) | |
.overlay { | |
Image(systemName: imageName) | |
.font(.title) | |
} | |
.cornerRadius(16) | |
.overlay { | |
RoundedRectangle(cornerRadius: 16) | |
.strokeBorder(Color.white.opacity(0.75), lineWidth: 1) | |
} | |
VStack(alignment: .leading, spacing: 6) { | |
Text(title) | |
.fontWeight(.bold) | |
Text(description) | |
.opacity(0.9) | |
} | |
.font(.title2) | |
} | |
.foregroundColor(.white) | |
.frame(maxWidth: .infinity, alignment: .leading) | |
.padding(20) | |
.background( | |
LinearGradient( | |
colors: [ | |
Color(color), | |
Color(color.offset(by: 0.06)), | |
], | |
startPoint: .topLeading, | |
endPoint: .bottomTrailing | |
) | |
) | |
.cornerRadius(20) | |
} | |
} | |
extension UIColor { | |
var hsba: (h: CGFloat, s: CGFloat, b: CGFloat, a: CGFloat) { | |
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 | |
self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) | |
return (h: h, s: s, b: b, a: a) | |
} | |
func offset(by offset: CGFloat) -> UIColor { | |
let (h, s, b, a) = hsba | |
var newHue = h - offset | |
/// make it go back to positive | |
while newHue <= 0 { | |
newHue += 1 | |
} | |
let normalizedHue = newHue.truncatingRemainder(dividingBy: 1) | |
return UIColor(hue: normalizedHue, saturation: s, brightness: b, alpha: a) | |
} | |
} | |
/// Use UIKit blurs in SwiftUI. | |
struct VisualEffectView: UIViewRepresentable { | |
/// The blur's style. | |
public var style: UIBlurEffect.Style | |
/// Use UIKit blurs in SwiftUI. | |
public init(_ style: UIBlurEffect.Style) { | |
self.style = style | |
} | |
public func makeUIView(context _: UIViewRepresentableContext<Self>) -> UIVisualEffectView { | |
UIVisualEffectView() | |
} | |
public func updateUIView(_ uiView: UIVisualEffectView, context _: UIViewRepresentableContext<Self>) { | |
uiView.effect = UIBlurEffect(style: style) | |
} | |
} |
Author
aheze
commented
Apr 30, 2022
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment