Skip to content

Commit

Permalink
Loading and Emprty state views
Browse files Browse the repository at this point in the history
  • Loading branch information
grenos committed Mar 28, 2020
1 parent 6efeebb commit 0b2582a
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 8 deletions.
56 changes: 50 additions & 6 deletions GHFollowers.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
7DA45F6F242BC47D00AB426F /* FollowerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DA45F6E242BC47D00AB426F /* FollowerCell.swift */; };
7DA45F71242BC5C100AB426F /* GFAvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DA45F70242BC5C100AB426F /* GFAvatarImageView.swift */; };
7DA45F73242E332000AB426F /* UIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DA45F72242E332000AB426F /* UIHelper.swift */; };
7DA45F7A242FB95700AB426F /* GFEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DA45F79242FB95700AB426F /* GFEmptyStateView.swift */; };
7DFAC88A242A86D200F5780C /* GFAlertContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DFAC889242A86D200F5780C /* GFAlertContainerView.swift */; };
/* End PBXBuildFile section */

Expand All @@ -53,6 +54,7 @@
7DA45F6E242BC47D00AB426F /* FollowerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerCell.swift; sourceTree = "<group>"; };
7DA45F70242BC5C100AB426F /* GFAvatarImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GFAvatarImageView.swift; sourceTree = "<group>"; };
7DA45F72242E332000AB426F /* UIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIHelper.swift; sourceTree = "<group>"; };
7DA45F79242FB95700AB426F /* GFEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GFEmptyStateView.swift; sourceTree = "<group>"; };
7DFAC889242A86D200F5780C /* GFAlertContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GFAlertContainerView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -112,13 +114,12 @@
7D98BB4E242A7BD5006C53E9 /* Components */ = {
isa = PBXGroup;
children = (
7DA45F78242FB8DB00AB426F /* Labels */,
7DA45F77242FB8C300AB426F /* ImageViews */,
7DA45F76242FB8A000AB426F /* TextFields */,
7DA45F75242FB89900AB426F /* Buttons */,
7DA45F74242FB89100AB426F /* Views */,
7DA45F6D242BC44F00AB426F /* Cells */,
7D98BB3F242917E4006C53E9 /* GFButton.swift */,
7D98BB4124291DF3006C53E9 /* GFTextField.swift */,
7D98BB45242A52B7006C53E9 /* GFTitleLabel.swift */,
7D98BB47242A5507006C53E9 /* GFBodyLabel.swift */,
7DFAC889242A86D200F5780C /* GFAlertContainerView.swift */,
7DA45F70242BC5C100AB426F /* GFAvatarImageView.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -176,6 +177,48 @@
path = Cells;
sourceTree = "<group>";
};
7DA45F74242FB89100AB426F /* Views */ = {
isa = PBXGroup;
children = (
7DFAC889242A86D200F5780C /* GFAlertContainerView.swift */,
7DA45F79242FB95700AB426F /* GFEmptyStateView.swift */,
);
path = Views;
sourceTree = "<group>";
};
7DA45F75242FB89900AB426F /* Buttons */ = {
isa = PBXGroup;
children = (
7D98BB3F242917E4006C53E9 /* GFButton.swift */,
);
path = Buttons;
sourceTree = "<group>";
};
7DA45F76242FB8A000AB426F /* TextFields */ = {
isa = PBXGroup;
children = (
7D98BB4124291DF3006C53E9 /* GFTextField.swift */,
);
path = TextFields;
sourceTree = "<group>";
};
7DA45F77242FB8C300AB426F /* ImageViews */ = {
isa = PBXGroup;
children = (
7DA45F70242BC5C100AB426F /* GFAvatarImageView.swift */,
);
path = ImageViews;
sourceTree = "<group>";
};
7DA45F78242FB8DB00AB426F /* Labels */ = {
isa = PBXGroup;
children = (
7D98BB45242A52B7006C53E9 /* GFTitleLabel.swift */,
7D98BB47242A5507006C53E9 /* GFBodyLabel.swift */,
);
path = Labels;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -251,6 +294,7 @@
7D98BB3E242907BB006C53E9 /* FavoriteListVC.swift in Sources */,
7D98BB40242917E4006C53E9 /* GFButton.swift in Sources */,
7D98BB3C24290771006C53E9 /* SearchVC.swift in Sources */,
7DA45F7A242FB95700AB426F /* GFEmptyStateView.swift in Sources */,
7D98BB4A242A55ED006C53E9 /* GFAlertVC.swift in Sources */,
7DA45F66242B760500AB426F /* User.swift in Sources */,
7DA45F6F242BC47D00AB426F /* FollowerCell.swift in Sources */,
Expand Down
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
64 changes: 64 additions & 0 deletions GHFollowers/Components/Views/GFEmptyStateView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// GFEmptyStateView.swift
// GHFollowers
//
// Created by Vasileios Gkreen on 28/03/2020.
// Copyright © 2020 Vasileios Gkreen. All rights reserved.
//

import UIKit

class GFEmptyStateView: UIView {

let messageLabel = GFTitleLabel(textAlignment: .center, fontSize: 28)
let logoImageView = UIImageView()

override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

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

// with the customs inits we can pass params when we call this view and initialize them
init(message: String) {
super.init(frame: .zero)
messageLabel.text = message
configure()
}


private func configure() {

addSubview(messageLabel)
addSubview(logoImageView)

messageLabel.numberOfLines = 3
messageLabel.textColor = .secondaryLabel

logoImageView.image = UIImage(named: "empty-state-logo")
logoImageView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
//center item on screen verrtically and then push up a litle bit
messageLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: -150),
messageLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 40),
messageLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -40),
// hard code height since we know the text message already
messageLabel.heightAnchor.constraint(equalToConstant: 200),

// take the image and make it 1.3 times larger then it curretn width (scale: 1.3)
logoImageView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.3),
// make height same as width (height will be 1.3 times larger)
logoImageView.heightAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.3),
// we use 200 to push the element TOWARDS the edge and not pull it like we did any other time
logoImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 170),
// same as above (push insted of pull) so not using a negative number in constant
logoImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 40)
])
}


}
59 changes: 58 additions & 1 deletion GHFollowers/Extensions/UIViewController+EXT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@

import UIKit


// We cant save variables inside an extension so we decalre a 'global' variable inside this file
// This global var with the keyword 'fileprivate' is available only in this file and not the entire programm
fileprivate var containerView: UIView!


// This extension is to be applied to all UIViewControllers used in this app
extension UIViewController {


/* we need to set this alert to be presented in the main thread because
sometimes we will need to call it from a background thread and it's ilegal to mount
UI elements called from the background thread
Expand All @@ -29,4 +34,56 @@ extension UIViewController {

}


func showLoadingView() {
// init container view and set it to fill the entire screen
containerView = UIView(frame: view.bounds)
// add the container view into the VC view (which ever is going to call this func)
view.addSubview(containerView)

containerView.backgroundColor = .systemBackground
containerView.alpha = 0

// animate alpha
UIView.animate(withDuration: 0.25) {
containerView.alpha = 0.8
}


// add activity indicator
let activityIndicator = UIActivityIndicatorView(style: .large)
containerView.addSubview(activityIndicator)

activityIndicator.translatesAutoresizingMaskIntoConstraints = false

// center indicator on containerView
NSLayoutConstraint.activate([
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor),
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])

activityIndicator.startAnimating()

}

func dismissLoadingView() {
// We will always going to dismiss Loading View from a background thread
// because we call this function from the Network manager closure (promise)
// to avoid that we need to throw it in the main thread
DispatchQueue.main.async {
containerView.removeFromSuperview()
containerView = nil
}
}


// calling this function from a VC we wiil pass the message and a the view so we know where to constrain it
func showEmptyStateView(with message: String, in view: UIView) {
let emptyStateView = GFEmptyStateView(message: message)
// fill up the entire screen
emptyStateView.frame = view.bounds
// add it to the VC subView
view.addSubview(emptyStateView)
}

}
19 changes: 18 additions & 1 deletion GHFollowers/Screens/FollowerListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,17 @@ class FollowerListVC: UIViewController {
// CHECK PLAYGROUND FOR DETAILS
// [week self] --> capture list
// when a self becomes weak the value of that becomes optional

// show activity indicator before the network call --- EXTENSIONS FILE ---
showLoadingView()

NetworkManager.shared.getFollowers(for: username, page: page) { [weak self] result in
// instead of adding self?. to all values below use guard for the self
guard let self = self else { return }

// call to dismiss the loading indicator --- EXTENSIONS FILE ---
self.dismissLoadingView()

switch result {
case .success(let followers):
// if no more follower turn switch off
Expand All @@ -114,6 +121,17 @@ class FollowerListVC: UIViewController {
// save the response in the followers array we declared and use append to save the next 100 results (concat, push)
self.followers.append(contentsOf: followers)
// call update data to keep the list updated with latest response

// control if array is returned empty and show empty state view
if self.followers.isEmpty {
let message = "This user doesn't have any followers. Go follow them! 😜"
DispatchQueue.main.async {
self.showEmptyStateView(with: message, in: self.view)
}
return
}

// call to update snapshot
self.updateData()

case .failure(let error):
Expand All @@ -138,7 +156,6 @@ extension FollowerListVC: UICollectionViewDelegate {
// get height of scrollView on screen
let scrollViewHeight = scrollView.frame.size.height


// if our offset is more than the entire scroll distance plus the screenheight
// it means we reached the end so we need to call the next page
if offsetY > contentHeight - scrollViewHeight {
Expand Down

0 comments on commit 0b2582a

Please sign in to comment.