Skip to content

Commit

Permalink
Merge pull request #33 from 3sidedcube/feature/post-view-order
Browse files Browse the repository at this point in the history
Create a `Toast` message which shows from the bottom. (Very much an iOS equivalent of an Android toast)
  • Loading branch information
BenShutt authored Aug 20, 2021
2 parents 65fba31 + 6d9d898 commit 5dd0ad9
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 82 deletions.
2 changes: 1 addition & 1 deletion Example/ViewControllers/MessageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class MessageViewController: UIViewController {

private lazy var messageStackView: MessageStackView = {
let messageStackView: MessageStackView = view.createMessageStackView()
messageStackView.messageConfiguation = MessageConfiguration(
messageStackView.messageConfiguration = MessageConfiguration(
backgroundColor: .systemGroupedBackground,
tintColor: .gray,
shadow: true,
Expand Down
3 changes: 2 additions & 1 deletion Example/ViewControllers/RootTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class RootTableViewController: UITableViewController {
("Badge Message", BadgeMessageViewController.self, false),
("Message", MessageViewController.self, false),
("No Internet", NoInternetTabBarController.self, true),
("Shadow", ShadowViewController.self, false)
("Shadow", ShadowViewController.self, false),
("Toast", ToastViewController.self, false)
]

// MARK: - ViewController lifecycle
Expand Down
52 changes: 52 additions & 0 deletions Example/ViewControllers/ToastViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// ToastViewController.swift
// Example
//
// Created by Ben Shutt on 02/06/2021.
// Copyright © 2021 3 SIDED CUBE APP PRODUCTIONS LTD. All rights reserved.
//

import Foundation
import UIKit
import MessageStackView

/// `UIViewController` to test `Toast`
class ToastViewController: UIViewController {

/// `Toast` to post at the bottom of the screen
private lazy var toast = Toast()

// MARK: - ViewController lifecycle

override func viewDidLoad() {
super.viewDidLoad()

toast.addTo(
view: view,
layout: .bottom,
constrainToSafeArea: true
)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

toast.post(message: .shortMessage)
toast.postIfNotShowing(message: .shortMessage) // Shouldn't show
toast.post(message: .longMessage)
}
}

// MARK: - String + Text

private extension String {

static let shortMessage = """
This is a toast!
"""

static let longMessage = """
This is a long toast to test how the text wraps when the width \
of the text is greater than the width of the screen
"""
}
12 changes: 12 additions & 0 deletions MessageStackView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
B81AEE5024FFF27A0068CE23 /* ShadowLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81AEE4F24FFF27A0068CE23 /* ShadowLayer.swift */; };
B81AEE5224FFFB2E0068CE23 /* ParentShadowLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81AEE5124FFFB2E0068CE23 /* ParentShadowLayer.swift */; };
B81AEE5425003F270068CE23 /* ShadowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81AEE5325003F270068CE23 /* ShadowViewController.swift */; };
B83CACB12667D5CE008FB755 /* Order.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83CACB02667D5CE008FB755 /* Order.swift */; };
B83CACB32667D97E008FB755 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83CACB22667D97E008FB755 /* Toast.swift */; };
B83CACB52667DA8A008FB755 /* ToastViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83CACB42667DA8A008FB755 /* ToastViewController.swift */; };
B872BE8725A3874C0031D619 /* UIImageView+Hidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = B872BE8625A3874C0031D619 /* UIImageView+Hidden.swift */; };
B872BE9125A387570031D619 /* UILabel+Hidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = B872BE9025A387570031D619 /* UILabel+Hidden.swift */; };
B875C98925126EB200FA05B5 /* UIViewController+System.swift in Sources */ = {isa = PBXBuildFile; fileRef = B875C98825126EB200FA05B5 /* UIViewController+System.swift */; };
Expand Down Expand Up @@ -172,6 +175,9 @@
B81AEE4F24FFF27A0068CE23 /* ShadowLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowLayer.swift; sourceTree = "<group>"; };
B81AEE5124FFFB2E0068CE23 /* ParentShadowLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentShadowLayer.swift; sourceTree = "<group>"; };
B81AEE5325003F270068CE23 /* ShadowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowViewController.swift; sourceTree = "<group>"; };
B83CACB02667D5CE008FB755 /* Order.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Order.swift; sourceTree = "<group>"; };
B83CACB22667D97E008FB755 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
B83CACB42667DA8A008FB755 /* ToastViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewController.swift; sourceTree = "<group>"; };
B872BE8625A3874C0031D619 /* UIImageView+Hidden.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Hidden.swift"; sourceTree = "<group>"; };
B872BE9025A387570031D619 /* UILabel+Hidden.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Hidden.swift"; sourceTree = "<group>"; };
B875C98825126EB200FA05B5 /* UIViewController+System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+System.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -369,6 +375,7 @@
B8BB087924C4BC46000E2E87 /* MessageViewController.swift */,
B87CC99C24CC263E002B697C /* NoInternetTabBarController.swift */,
B81AEE5325003F270068CE23 /* ShadowViewController.swift */,
B83CACB42667DA8A008FB755 /* ToastViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -519,6 +526,7 @@
OBJ_54 /* PostAnimation.swift */,
OBJ_55 /* Queue.swift */,
B80B92AA2527324400F97364 /* Vector3.swift */,
B83CACB02667D5CE008FB755 /* Order.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -595,6 +603,7 @@
OBJ_74 /* MessageStackView.swift */,
OBJ_75 /* PostView.swift */,
B8F1C76224D8198E007C2712 /* ApplicationPostView.swift */,
B83CACB22667D97E008FB755 /* Toast.swift */,
);
path = PostView;
sourceTree = "<group>";
Expand Down Expand Up @@ -812,6 +821,7 @@
B8BB088424C4BC46000E2E87 /* WindowViewController.swift in Sources */,
B8BB088724C4BC46000E2E87 /* MessageViewController.swift in Sources */,
B8BB088224C4BC46000E2E87 /* RootTableViewController.swift in Sources */,
B83CACB52667DA8A008FB755 /* ToastViewController.swift in Sources */,
B87CC99D24CC263E002B697C /* NoInternetTabBarController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -854,6 +864,7 @@
OBJ_99 /* DispatchQueue+Extensions.swift in Sources */,
OBJ_100 /* String+Extensions.swift in Sources */,
OBJ_101 /* UIApplication+StatusBar.swift in Sources */,
B83CACB32667D97E008FB755 /* Toast.swift in Sources */,
OBJ_102 /* UIEdgeInsets+Extensions.swift in Sources */,
OBJ_103 /* UIView+Animation.swift in Sources */,
B8BCDA8C24CDB55B0067F26C /* UIViewController+Lifecycle.m in Sources */,
Expand All @@ -874,6 +885,7 @@
B8F1C76324D8198E007C2712 /* ApplicationPostView.swift in Sources */,
B81727F024D6CC9F0062282B /* PostViewController.swift in Sources */,
B8E1CEEF2500F88C002BFE77 /* NeuomorphicShadow.swift in Sources */,
B83CACB12667D5CE008FB755 /* Order.swift in Sources */,
OBJ_115 /* BadgeMessageView.swift in Sources */,
OBJ_116 /* BadgeMessageViewable.swift in Sources */,
OBJ_117 /* Poster+BadgeMessage.swift in Sources */,
Expand Down
13 changes: 13 additions & 0 deletions Sources/Constraints/MessageLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ public enum MessageLayout: Int {
case bottom
}

// MARK: - Order

public extension MessageLayout {

/// Map to `Order`
func toOrder() -> Order {
switch self {
case .top: return .topToBottom
case .bottom: return .bottomToTop
}
}
}

// MARK: - NSLayoutConstraint

public extension MessageLayout {
Expand Down
35 changes: 35 additions & 0 deletions Sources/Models/Order.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Order.swift
// MessageStackView
//
// Created by Ben Shutt on 02/06/2021.
// Copyright © 2021 3 SIDED CUBE APP PRODUCTIONS LTD. All rights reserved.
//

import Foundation

/// How posted `UIView`s are ordered.
/// E.g. the order of the `arrangedSubviews`.
public enum Order {

/// Natural order of `UIStackView`s. Posted `UIView`s get appended to the
/// `arrangedSubviews` array appearing below/after the previous.
case topToBottom

/// Reverse order of `UIStackView`s. Posted `UIView`s get inserted at the start of the
/// `arrangedSubviews` array appearing above/before the previous.
case bottomToTop
}

// MARK: - Extensions

public extension Order {

/// Other `Order` (opposite direction)
var switched: Self {
switch self {
case .topToBottom: return .bottomToTop
case .bottomToTop: return .topToBottom
}
}
}
33 changes: 24 additions & 9 deletions Sources/PostManager/PostGestureManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protocol PostGestureManagerDelegate: AnyObject {

/// Manage gestures for posted `UIView`s to trigger a removal of that posted `UIView`.
///
/// E.g. The user pans a view off the top wanting for it to be dimissed.
/// E.g. The user pans a view off of the top wanting for it to be dismissed.
/// E.g. T user tapped to dismiss
public class PostGestureManager {

Expand All @@ -43,6 +43,12 @@ public class PostGestureManager {
/// Map `UIView`s to their "pan to dismiss" `UIPanGestureRecognizer`
private var panGestureMap = [UIView: UIPanGestureRecognizer]()

/// `Order` to configure which direction the user is allowed to pan.
///
/// - When `.topToBottom`, the user can pan/swipe up to dismiss
/// - When `.bottomToTop`, the user can pan/swipe down to dismiss
var order: Order = .topToBottom

// MARK: - Deinit

deinit {
Expand All @@ -51,6 +57,7 @@ public class PostGestureManager {

// MARK: - Invalidate

/// Invalidate maps
func invalidate() {
tapGestureMap.forEach {
$0.key.removeGestureRecognizer($0.value)
Expand Down Expand Up @@ -152,19 +159,15 @@ public class PostGestureManager {

// Pan did update
case .changed:
view.transform = CGAffineTransform(
translationX: 0,
y: min(0, sender.translation(in: view.superview).y)
)
let ty = sender.translation(in: view.superview).y
let y = order.translationY(ty)
view.transform = CGAffineTransform(translationX: 0, y: y)
return

// Pan did finish
case .ended:
let translateY = view.transform.ty
let centerY = view.center.y

let finalY = centerY + translateY
if finalY < 0 {
if abs(translateY) > view.bounds.size.height * 0.5 {
requestRemove(view: view)
return
}
Expand Down Expand Up @@ -220,3 +223,15 @@ private extension UIGestureRecognizer.State {
}
}
}

// MARK: - Order + Translation

private extension Order {

func translationY(_ ty: CGFloat) -> CGFloat {
switch self {
case .topToBottom: return min(0, ty)
case .bottomToTop: return max(0, ty)
}
}
}
13 changes: 7 additions & 6 deletions Sources/PostManager/PostManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class PostManager {
/// `UIViewPoster` to handle the post and remove of a `PostRequest`
private weak var poster: UIViewPoster?

/// `PostManagerDelegate` for `PostManager` delegate callabacks
/// `PostManagerDelegate` for `PostManager` delegate callbacks
public weak var delegate: PostManagerDelegate?

/// `PostGestureManager` to manage `UIGestureRecognizer` actions
Expand All @@ -48,7 +48,7 @@ public class PostManager {

/// Should the posted `UIView`s be handled by a serial `Queue`, i.e. one at a time.
/// If `true`, queued `PostRequest`s are stored in `queue`.
public var isSerialQueue: Bool = true {
public var isSerialQueue = true {
didSet {
guard !isSerialQueue else { return }
while let postRequest = queue.dequeue() {
Expand Down Expand Up @@ -107,10 +107,10 @@ public class PostManager {

/// Is the `PostManager` posting or scheduled to post
public var isActive: Bool {
// A post is showing atm
// Is a post is showing at the moment
let isPosting = !currentPostRequests.isEmpty

// A post is scheduled to show in the future
// Is a post is scheduled to show in the future
let isQueued = !queue.isEmpty

return isPosting || isQueued
Expand Down Expand Up @@ -157,7 +157,8 @@ public class PostManager {
animated: postRequest.animated.contains(.onPost),
completion: {
self.completePost(postRequest: postRequest)
})
}
)
}

/// Invoke on the completion of `poster` posting a `view` of a `postRequest`.
Expand Down Expand Up @@ -217,7 +218,7 @@ public class PostManager {
)
}

/// Remove (unpost) a posted `view` invalidatating appropriate properties.
/// Remove (un-post) a posted `view` invalidating appropriate properties.
/// - Parameters:
/// - view: `UIView` to remove
/// - animated: Should the removal be animated
Expand Down
4 changes: 2 additions & 2 deletions Sources/PostManager/UIView+Poster.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// UIView+MessageStackView.swift
// UIView+Poster.swift
// MessageStackView
//
// Created by Ben Shutt on 03/07/2020.
Expand Down Expand Up @@ -48,7 +48,7 @@ public extension UIView {
layout: layout,
constrainToSafeArea: constrainToSafeArea
)
messageStackView.updateOrderForLayout(layout)
messageStackView.order = layout.toOrder()
return messageStackView
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public extension ConnectivityManager {
/// `MessageStackView ` to post messages
public private(set) lazy var messageStackView: MessageStackView = {
let messageStackView = MessageStackView()
messageStackView.updateOrderForLayout(.bottom)
messageStackView.order = MessageLayout.bottom.toOrder()
messageStackView.postManager.delegate = self
return messageStackView
}()
Expand Down
2 changes: 1 addition & 1 deletion Sources/Reachability/ConnectivityViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ open class ConnectivityViewController: UIViewController, ConnectivityMessageable
layout: messageLayout,
constrainToSafeArea: false // Inset spaceView height
)
messageStackView.updateOrderForLayout(messageLayout)
messageStackView.order = messageLayout.toOrder()
view.setNeedsLayout()
}

Expand Down
Loading

0 comments on commit 5dd0ad9

Please sign in to comment.