Skip to content

Commit

Permalink
Create DGSnackBar
Browse files Browse the repository at this point in the history
  • Loading branch information
donggyushin committed Jan 7, 2022
1 parent 5d6ccbf commit 5cd16cd
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
31 changes: 31 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "DGSnackBar",
platforms: [
.iOS(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "DGSnackBar",
targets: ["DGSnackBar"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "DGSnackBar",
dependencies: []),
.testTarget(
name: "DGSnackBarTests",
dependencies: ["DGSnackBar"]),
]
)
62 changes: 62 additions & 0 deletions Sources/DGSnackBar/DGSnackBar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import UIKit

public class DGSnackBar {
public static let shared = DGSnackBar()

init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)

NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}

public var backgroundColor: UIColor = .label
public var textColor: UIColor = UIColor.systemBackground
public var duration: CGFloat = 2.5
public var textAlignment: NSTextAlignment = .natural
public var alpha: CGFloat = 0.8
public var cornerRadius: CGFloat = 10

private var snackBarView: SnackBarView?
private var keyboardHeight: CGFloat = 0
private var isKeyboardPresent = false

public func showToast(_ text: String) {
guard let window = UIApplication.shared.connectedScenes.compactMap({ ($0 as? UIWindowScene) }).flatMap({ $0.windows }).first(where: { $0.isKeyWindow }) else { return }
let snackBarView = SnackBarView(descriptionString: text, backgroundColor: backgroundColor, textColor: textColor, duration: duration, textAlignment: textAlignment, alpha: alpha, cornerRadius: cornerRadius)
self.snackBarView = snackBarView
window.subviews.compactMap({ $0 as? SnackBarView }).forEach({ $0.hideFading() })
window.addSubview(snackBarView)
snackBarView.translatesAutoresizingMaskIntoConstraints = false
snackBarView.bottomAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: -10 - keyboardHeight).isActive = true
snackBarView.leftAnchor.constraint(equalTo: window.leftAnchor, constant: 20).isActive = true
snackBarView.rightAnchor.constraint(equalTo: window.rightAnchor, constant: -20).isActive = true
}

@objc private func keyboardWillHide(_ notification: Notification) {
if !self.isKeyboardPresent { return }
snackBarView?.frame.origin.y += keyboardHeight
self.keyboardHeight = 0
self.isKeyboardPresent = false
}

@objc private func keyboardWillShow(_ notification: Notification) {
if self.isKeyboardPresent { return }
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
self.keyboardHeight = keyboardHeight
self.snackBarView?.frame.origin.y -= keyboardHeight
self.isKeyboardPresent = true
}
}
}
90 changes: 90 additions & 0 deletions Sources/DGSnackBar/SnackBarView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// SnackBarView.swift
// DGSnackBarExample
//
// Created by 신동규 on 2022/01/07.
//
import UIKit

class SnackBarView: UIView {

private lazy var descriptionLabel: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
view.text = descriptionString
view.numberOfLines = 4
view.textColor = textColor
view.textAlignment = textAlignment
return view
}()

private lazy var verticalStackView: UIStackView = {
let view = UIStackView(arrangedSubviews: [descriptionLabel])
view.axis = .vertical
return view
}()

private let descriptionString: String
private let textColor: UIColor
private let textAlignment: NSTextAlignment
private let _backgroundColor: UIColor
private let _alpha: CGFloat
private let duration: CGFloat
private let cornerRadius: CGFloat

init(descriptionString: String, backgroundColor: UIColor = .label, textColor: UIColor = UIColor.systemBackground, duration: CGFloat = 2.5, textAlignment: NSTextAlignment = .natural, alpha: CGFloat = 1, cornerRadius: CGFloat = 10) {
self.descriptionString = descriptionString
self.textColor = textColor
self.textAlignment = textAlignment
self._backgroundColor = backgroundColor
self._alpha = alpha
self.duration = duration
self.cornerRadius = cornerRadius
super.init(frame: .zero)
configureUI()
}

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

private func configureUI() {
alpha = _alpha
backgroundColor = _backgroundColor
transform = .init(translationX: 0, y: 200)
layer.cornerRadius = cornerRadius
clipsToBounds = true
addSubview(verticalStackView)
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
verticalStackView.leftAnchor.constraint(equalTo: leftAnchor, constant: 20).isActive = true
verticalStackView.rightAnchor.constraint(equalTo: rightAnchor, constant: -20).isActive = true
verticalStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
present()
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
self.hide()
}
}

private func present() {
UIView.animate(withDuration: 0.4) {
self.transform = .init(translationX: 0, y: 0)
}
}

private func hide() {
UIView.animate(withDuration: 0.4) {
self.transform = .init(translationX: 0, y: 200)
} completion: { _ in
self.removeFromSuperview()
}
}

func hideFading() {
UIView.animate(withDuration: 0.4) {
self.alpha = 0
} completion: { _ in
self.removeFromSuperview()
}
}
}
11 changes: 11 additions & 0 deletions Tests/DGSnackBarTests/DGSnackBarTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XCTest
@testable import DGSnackBar

final class DGSnackBarTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertTrue(true)
}
}

0 comments on commit 5cd16cd

Please sign in to comment.