11
22import UIKit
33
4- /// Shows a view with a message and a standard empty search results image .
4+ /// A configurable view to display an " empty state" .
55///
6- /// This is generally used with `SearchUICommand`.
6+ /// This can show (from top to bottom):
77///
8- final class EmptySearchResultsViewController : UIViewController , KeyboardFrameAdjustmentProvider {
8+ /// - A message
9+ /// - An image
10+ /// - A label suitable for a longer message
11+ /// - An action button
12+ ///
13+ /// These elements are hidden by default and can be configured and shown using
14+ /// the `configure` method.
15+ ///
16+ final class EmptyStateViewController : UIViewController , KeyboardFrameAdjustmentProvider {
17+
18+ /// The submitted argument when configuring the `actionButton`.
19+ ///
20+ struct ActionButtonConfig {
21+ let title : String
22+ let onTap : ( ) -> ( )
23+ }
924
25+ /// The main message shown at the top.
26+ ///
1027 @IBOutlet private var messageLabel : UILabel ! {
1128 didSet {
1229 // Remove dummy text in Interface Builder
1330 messageLabel. text = nil
31+ messageLabel. isHidden = true
32+ }
33+ }
34+
35+ /// An image shown below the message.
36+ ///
37+ @IBOutlet private var imageView : UIImageView ! {
38+ didSet {
39+ imageView. image = nil
40+ imageView. isHidden = true
41+ }
42+ }
43+
44+ /// Additional text shown below the image.
45+ ///
46+ @IBOutlet private var detailsLabel : UILabel ! {
47+ didSet {
48+ detailsLabel. text = nil
49+ detailsLabel. isHidden = true
1450 }
1551 }
16- @IBOutlet private var imageView : UIImageView !
52+
53+ /// The button shown below the detail text.
54+ ///
55+ @IBOutlet private var actionButton : UIButton ! {
56+ didSet {
57+ actionButton. setTitle ( nil , for: . normal)
58+ actionButton. isHidden = true
59+ }
60+ }
61+
62+ /// The scrollable view containing all the content (labels, image, etc).
63+ ///
1764 @IBOutlet private var scrollView : UIScrollView !
1865
1966 /// The height adjustment constraint for the content view.
@@ -26,6 +73,10 @@ final class EmptySearchResultsViewController: UIViewController, KeyboardFrameAdj
2673 ///
2774 @IBOutlet private var contentViewHeightAdjustmentFromSuperviewConstraint : NSLayoutConstraint !
2875
76+ /// The last `ActionButtonConfig` passed during `configure()`
77+ ///
78+ private var lastActionButtonConfig : ActionButtonConfig ?
79+
2980 private lazy var keyboardFrameObserver = KeyboardFrameObserver ( onKeyboardFrameUpdate: { [ weak self] frame in
3081 self ? . handleKeyboardFrameUpdate ( keyboardFrame: frame)
3182 self ? . verticallyAlignStackViewUsing ( keyboardHeight: frame. height)
@@ -48,9 +99,9 @@ final class EmptySearchResultsViewController: UIViewController, KeyboardFrameAdj
4899
49100 view. backgroundColor = . basicBackground
50101
51- imageView. image = . emptySearchResultsImage
52-
53102 messageLabel. applyBodyStyle ( )
103+ detailsLabel. applySecondaryBodyStyle ( )
104+ actionButton. applyPrimaryButtonStyle ( )
54105
55106 keyboardFrameObserver. startObservingKeyboardFrame ( sendInitialEvent: true )
56107 }
@@ -61,12 +112,26 @@ final class EmptySearchResultsViewController: UIViewController, KeyboardFrameAdj
61112 updateImageVisibilityUsing ( traits: traitCollection)
62113 }
63114
64- /// Change the message being displayed.
115+ /// Change the elements being displayed.
65116 ///
66117 /// This is the only "configurable" point for consumers using this class.
67118 ///
68- func configure( message: NSAttributedString ? ) {
119+ func configure( message: NSAttributedString ? = nil ,
120+ image: UIImage ? = nil ,
121+ details: String ? = nil ,
122+ actionButton actionButtonConfig: ActionButtonConfig ? = nil ) {
69123 messageLabel. attributedText = message
124+ messageLabel. isHidden = message == nil
125+
126+ imageView. image = image
127+ imageView. isHidden = image == nil
128+
129+ detailsLabel. text = details
130+ detailsLabel. isHidden = details == nil
131+
132+ lastActionButtonConfig = actionButtonConfig
133+ actionButton. setTitle ( actionButtonConfig? . title, for: . normal)
134+ actionButton. isHidden = actionButtonConfig == nil
70135 }
71136
72137 /// Watch for device orientation changes and update the `imageView`'s visibility accordingly.
@@ -83,7 +148,8 @@ final class EmptySearchResultsViewController: UIViewController, KeyboardFrameAdj
83148 /// Hide the `imageView` if there is not enough vertical space (e.g. iPhone landscape).
84149 ///
85150 private func updateImageVisibilityUsing( traits: UITraitCollection ) {
86- let shouldShowImageView = traits. verticalSizeClass != . compact
151+ let shouldShowImageView = traits. verticalSizeClass != . compact &&
152+ imageView. image != nil
87153 imageView. isHidden = !shouldShowImageView
88154 }
89155
@@ -109,11 +175,16 @@ final class EmptySearchResultsViewController: UIViewController, KeyboardFrameAdj
109175
110176 contentViewHeightAdjustmentFromSuperviewConstraint. constant = constraintConstant
111177 }
178+
179+ /// OnTouchUpInside handler for the `actionButton`.
180+ @IBAction private func actionButtonTapped( _ sender: Any ) {
181+ lastActionButtonConfig? . onTap ( )
182+ }
112183}
113184
114185// MARK: - KeyboardScrollable
115186
116- extension EmptySearchResultsViewController : KeyboardScrollable {
187+ extension EmptyStateViewController : KeyboardScrollable {
117188 var scrollable : UIScrollView {
118189 scrollView
119190 }
0 commit comments