Skip to content

Commit

Permalink
feat: add new implementation of snackbar
Browse files Browse the repository at this point in the history
  • Loading branch information
juandahurt committed Nov 26, 2022
1 parent 6c903d3 commit ec4448f
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 5 deletions.
8 changes: 3 additions & 5 deletions PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ import SwiftUI
import Purace

struct SnackBarExample: View {
@State var showSnackbar = false

var body: some View {
VStack {
Text("`PuraceSnackbarView(title: ...)`")
.padding()
PuraceButtonView(!showSnackbar ? "Mostrar snackbar" : "Ocultar snackbar", fontSize: 14) {
showSnackbar = !showSnackbar
PuraceButtonView("Mostrar snackbar", fontSize: 14) {
PuraceSnackbarManager.instance.show(withTitle: "Test", type: .info)
}
Spacer()
}.snackBar(title: "Parece que ha ocurrido un error", isVisible: $showSnackbar, type: .info, buttonTitle: "REINTENTAR", duration: .long, dismissOnDrag: true)
}
}
}
18 changes: 18 additions & 0 deletions Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// UIApplication+keyWindow.swift
//
//
// Created by Juan Hurtado on 21/11/22.
//

import UIKit

extension UIApplication {
var keyWindow: UIWindow? {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first(where: { $0 is UIWindowScene })
.flatMap({ $0 as? UIWindowScene })?.windows
.first(where: \.isKeyWindow)
}
}
34 changes: 34 additions & 0 deletions Sources/Purace/Common/Extensions/UIView+NibLoadable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// File.swift
//
//
// Created by Juan Hurtado on 19/11/22.
//

import Foundation
import UIKit

public protocol NibLoadable {
static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {
static var nibName: String {
return String(describing: Self.self)
}

static var nib: UINib {
let bundle = Bundle.module
return UINib(nibName: Self.nibName, bundle: bundle)
}

func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// PuraceSnackbar.swift
//
//
// Created by Juan Hurtado on 19/11/22.
//

import UIKit

class PuraceSnackbar: UIView, NibLoadable {
@IBOutlet var contentView: UIView!
@IBOutlet weak var actionButton: UIButton!
@IBOutlet weak var titleLabel: UILabel!

override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
translatesAutoresizingMaskIntoConstraints = false
setupSubviews()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func setupSubviews() {
titleLabel.font = UIFont(name: "Poppins-Regular", size: 14)
titleLabel.textColor = .white
actionButton.titleLabel?.textColor = .white
actionButton.titleLabel?.font = UIFont(name: "Poppins-Medium", size: 14)
contentView.layer.cornerRadius = 10
}

func setup(with title: String, type: PuraceSnackbarType) {
switch type {
case .info:
contentView.backgroundColor = .init(PuraceStyle.Color.G1)
case .alert:
contentView.backgroundColor = .init(PuraceStyle.Color.B1)
case .error:
contentView.backgroundColor = .init(PuraceStyle.Color.R1)
}
titleLabel.text = title
}
}

// MARK: - Config constants
extension PuraceSnackbar {
static let height: CGFloat = 60
static let padding: CGFloat = 20
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PuraceSnackbar" customModuleProvider="target">
<connections>
<outlet property="actionButton" destination="nek-Ra-1ia" id="c3m-MM-Pc2"/>
<outlet property="contentView" destination="fTQ-8B-eqd" id="d4d-wB-YV5"/>
<outlet property="titleLabel" destination="Vd3-Fo-KXQ" id="56M-lz-Bvd"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="fTQ-8B-eqd">
<rect key="frame" x="0.0" y="0.0" width="393" height="134"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vd3-Fo-KXQ">
<rect key="frame" x="20" y="56.666666666666657" width="42" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nek-Ra-1ia">
<rect key="frame" x="319" y="50" width="54" height="34"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Button"/>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Vd3-Fo-KXQ" firstAttribute="centerY" secondItem="fTQ-8B-eqd" secondAttribute="centerY" id="Icc-dL-4eH"/>
<constraint firstAttribute="trailing" secondItem="nek-Ra-1ia" secondAttribute="trailing" constant="20" id="W4V-zZ-bMS"/>
<constraint firstItem="nek-Ra-1ia" firstAttribute="centerY" secondItem="fTQ-8B-eqd" secondAttribute="centerY" id="YO9-ZV-LCy"/>
<constraint firstItem="Vd3-Fo-KXQ" firstAttribute="leading" secondItem="fTQ-8B-eqd" secondAttribute="leading" constant="20" id="iaC-gl-QId"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="319.84732824427482" y="292.25352112676057"/>
</view>
</objects>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// PuraceSnackbarConfig.swift
//
//
// Created by Juan Hurtado on 21/11/22.
//

import Foundation

struct PuraceSnackbarConfig {
let title: String
let type: PuraceSnackbarType = .info
let action: (() -> Void)?
let actionTitle: String?
}
110 changes: 110 additions & 0 deletions Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// PuraceSnackbarManager.swift
//
//
// Created by Juan Hurtado on 18/11/22.
//

import Foundation
import UIKit

public class PuraceSnackbarManager {
private static var _instance: PuraceSnackbarManager? = nil
public static var instance: PuraceSnackbarManager {
if _instance == nil {
_instance = PuraceSnackbarManager()
}
return _instance!
}

private var verbose = false

var isPresented = false
var queue = [() -> ()]()
var snackbar: PuraceSnackbar?

private init() {}

private func setupSnackbarView(withTitle title: String, type: PuraceSnackbarType) {
guard let window = UIApplication.shared.keyWindow else { return }
let bottomPadding = window.safeAreaInsets.bottom

snackbar = PuraceSnackbar(frame: .init(x: 0, y: 0, width: 100, height: 0))
snackbar?.setup(with: title, type: type)
window.addSubview(snackbar!)

// Height constriant
snackbar?.heightAnchor.constraint(equalToConstant: PuraceSnackbar.height).isActive = true

// Bottom constriant
snackbar?.topAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: bottomPadding).isActive = true

// Left constraint
snackbar?.leadingAnchor.constraint(equalTo: window.leadingAnchor, constant: PuraceSnackbar.padding).isActive = true
// Right constraint
snackbar?.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: -PuraceSnackbar.padding).isActive = true
}


/// When called this function, some verbose logs will be printed on the console.
///
/// Call this function **only** for debug purpuses.
public func debug() -> PuraceSnackbarManager {
verbose = true
return self
}

public func show(withTitle title: String, type: PuraceSnackbarType) {
if isPresented {
enqueue(label: "show") { [weak self] in
guard let self else { return }
self.show(withTitle: title, type: type)
}
return
}

if verbose {
print("showing snackbar")
}

setupSnackbarView(withTitle: title, type: type)
UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in
guard let self else { return }
self.snackbar?.transform = .init(translationX: 0, y: -PuraceSnackbar.height - PuraceSnackbar.padding)
}) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self else { return }
if self.isPresented {
self.hide()
}
}
}
isPresented = true

}

public func hide() {
if verbose {
print("hiding snackbar")
}
guard isPresented else { return }
UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in
guard let self else { return }
self.snackbar?.transform = .init(translationX: 0, y: PuraceSnackbar.height)
}) { _ in
self.snackbar?.removeFromSuperview()
self.isPresented = false
guard !self.queue.isEmpty else { return }
let action = self.queue.removeFirst()
action()
}
}

private func enqueue(label: String, action: @escaping () -> ()) {
queue.append(action)
if verbose {
print("Enqueuing action: \(label)")
print("queue length: \(queue.count)")
}
}
}

0 comments on commit ec4448f

Please sign in to comment.