Now I want to implement the detect network from the beginning into the app will detect whether the link network,assuming that the user cuts off the network on the way, the screen will display a label and imageView, if the network is reconnected, you can automatically display the information and let label and imageView disappear, but now I do not know how to automatically detect Network, and label and imageView how to put the center of the screen (not using storyboard), thank you!
and here is my code:
func laodingTableviewData() {
self.tableView.isHidden = true
let activityIndicatorView = NVActivityIndicatorView(frame:CGRect(x:ActivityConstraint.Activity.offsetX, y:ActivityConstraint.Activity.offsetY , width:80.0, height:80.0), type: .ballSpinFadeLoader, color: ActivityConstraint.Activity.color, padding: 20)
self.view.addSubview(activityIndicatorView)
activityIndicatorView.startAnimating()
//detect network
guard Reachability.isConnectedToNetwork() == true else {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
self.failNetworkAlert()
self.failNetworkOutlet()
activityIndicatorView.stopAnimating()
}
return
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
self.bulletinBoards = BulletinBoard.downloadAllBulletinBoard()
self.tableView.isHidden = false
self.tableView.reloadData()
activityIndicatorView.stopAnimating()
}
}
Create label and imageView:
func failNetworkOutlet() {
let w = self.view.frame.width/2
let h = self.view.frame.height/2
let label = UILabel(frame: CGRect(x: -30, y: 0, width: 250, height: 21))
label.center = CGPoint(x: w+5, y: h)
label.textAlignment = .center
label.text = "Please confirm that you are connected to the web"
label.font = label.font.withSize(20)
label.tintColor = UIColor.gray
let imageViewObject = UIImageView(frame:CGRect(x: w-25, y: h-85, width: 60, height: 60))
imageViewObject.image = UIImage(named:"ic_settings_input_antenna_48pt")?.withRenderingMode(.alwaysTemplate)
imageViewObject.tintColor = UIColor.gray
self.view.addSubview(imageViewObject)
self.view.addSubview(label)
}
You can check for the network notifications from reachability. Then post some sorts of notification so that we would know the network status
import UIKit
import ReachabilitySwift
class ViewController: UIViewController {
let reachability = Reachability()
override func viewDidLoad() {
super.viewDidLoad()
checkNetworkStatus()
}
fileprivate func checkNetworkStatus() {
if let reachability = reachability {
reachability.whenReachable = { [unowned self] reachable in
self.postNetworkStatusChangedNotification(to: true)
}
reachability.whenUnreachable = { [unowned self] unreachable in
self.postNetworkStatusChangedNotification(to: false)
}
//start the notifier
do {
try reachability.startNotifier()
}
catch {
print("Unable to start the network notifier")
}
}
}
func postNetworkStatusChangedNotification(to acquired:Bool) {
//we will post notification
if acquired {
print("\n=== Internet Acess Acquired ===\n")
}
else {
print("\n=== Lost Internet Access ===\n")
}
}
}
When this is started it will notify whenever network status has been changed.
Related
class MainViewController: UIViewController {
let button = UIButton()
let mainViewModel = MainViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
bind()
}
func bind() {
button.rx.tap
.bind(to: mainViewModel.doAction)
.disposed(by: disposeBag)
mainViewModel.somethingDoneObservable
.observeOn(MainScheduler.instance)
.flatMapLatest { [weak self] value -> Observable<String> in
print(value)
let vc = Work1ViewController()
self?.present(vc, animated: true, completion: nil)
return vc.work1ViewModel.work1DoneObservable
}
.flatMapLatest { [weak self] value -> Observable<String> in
print(value)
let vc = Work2ViewController()
self?.present(vc, animated: true, completion: nil)
return vc.work2ViewModel.work2DoneObservable
}
.subscribe(onNext: { newValue in
// do something
// all done
print(newValue)
})
.disposed(by: disposeBag)
}
}
class MainViewModel {
let doAction = PublishSubject<Void>()
let somethingDoneObservable = PublishSubject<String>()
let disposeBag = DisposeBag()
init() {
doAction
.subscribe(onNext: {
// do something
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(5000)
DispatchQueue.main.asyncAfter(deadline: when) {
self.somethingDoneObservable.onNext("Something Done!!")
}
})
.disposed(by: disposeBag)
}
}
// ----------------------------------------------------------------------
class Work1ViewController: UIViewController {
let button = UIButton()
let work1ViewModel = Work1ViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
bind()
}
func bind() {
button.rx.tap
.bind(to: work1ViewModel.doAction)
.disposed(by: disposeBag)
work1ViewModel.work1DoneObservable
.subscribe(onNext: { [weak self] _ in
self?.dismiss(animated: false, completion: nil)
})
.disposed(by: disposeBag)
}
}
class Work1ViewModel {
let doAction = PublishSubject<Void>()
let work1DoneObservable = PublishSubject<String>()
let disposeBag = DisposeBag()
init() {
doAction
.subscribe(onNext: {
// do work1
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(5000)
DispatchQueue.main.asyncAfter(deadline: when) {
self.work1DoneObservable.onNext("Work1 Done!!")
}
})
.disposed(by: disposeBag)
}
}
(Work1, Work2ViewController can be dismissed without doAction event.)
as you see, I want to connet several work steps.(work1 -> work2 -> mainVc)
in this case, I used flatMapLatest.
is it correct way? if not, is there common way or other elegant way?
if it is correct, is there any problem?(memory leak or ..)
if I tap MainViewController'button later, is it ok?
There are problems with this specific implementation, but you are on the right track. Consider using my CLE library which takes care of all the gotchas involved. It can be installed using CocoaPods or SPM.
Using flatMapLatest is okay but I think flatMapFirst is better. The difference is in how the system interprets subsequent taps of the button. The flatMapLatest operator will dispose and resubscribe to the inner observable, which in this case will attempt to present viewController1 on top of itself. You will see this as a problem if you tap the button a couple of times before the new view controller presents itself.
There are other problems. If the user dismisses view controller 1 any way other than tapping the button (by swiping down, for example,) there will be a resource leak. Also, you never emit completed events when in your view controllers dismiss which causes resource leaks as well.
No.
With my library, all the possible ways to dismiss a view controller are accounted for and there are no leaks. The code would look like this:
final class MainViewController: UIViewController {
let button = UIButton()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
button.backgroundColor = .blue
}
}
extension MainViewController {
func bind() {
let somethingDone = button.rx.tap
.flatMapFirst {
doAction()
}
let actionDone = somethingDone
.flatMapFirst(presentScene(animated: true, scene: { _ in actionFlow() }))
actionDone
.subscribe(onNext: {
print("all done!")
})
.disposed(by: disposeBag)
}
}
func doAction() -> Observable<String> {
Observable.create { observer in
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(5000)
DispatchQueue.main.asyncAfter(deadline: when) {
observer.onSuccess("Something Done!!")
}
return Disposables.create()
}
}
func actionFlow() -> Scene<Void> {
let work1 = Work1ViewController().scene { $0.bind() }
let work2 = work1.action
.flatMapFirst(presentScene(animated: true, scene: { _ in
Work2ViewController().scene { $0.bind() }
}))
.take(1)
return Scene(controller: work1.controller, action: work2)
}
//------------------------------------------------------------------------------
final class Work1ViewController: UIViewController {
let button = UIButton()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
button.backgroundColor = .blue
}
}
extension Work1ViewController {
func bind() -> Observable<String> {
return button.rx.tap
.flatMapFirst { doAction() }
}
}
final class Work2ViewController: UIViewController {
let button = UIButton()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
button.backgroundColor = .blue
}
}
extension Work2ViewController {
func bind() -> Observable<Void> {
button.rx.tap.asObservable()
}
}
I have a small snippet of code to dismiss the presented VC:
UIView.animate(withDuration: 0.3, animations: { [weak self] in
guard let self = self else {
return
}
DispatchQueue.main.async {
self.stackViewMain.frame = CGRect(origin: CGPoint(x: self.stackViewMain.frame.origin.x, y: self.view.bounds.height), size: self.stackViewMain.bounds.size)
}
}) { _ in
weak var weakSelf = self
self.dismiss(animated: true, completion: {
weakSelf?.viewModel.animationdDidFinishControllerWillDismiss()
})
}
So the issue is in this part:
self.dismiss(animated: true, completion: {
weakSelf?.viewModel.animationdDidFinishControllerWillDismiss()
})
I just found out that self.dismiss is not being run and completionHandler is not being fired on iOS 12 but everything works as expected on iOS 13.
Can someone explain me why it does not work and how can I fix it?
Animation part is wrong.
You don't need to use DispatchQueue. Animation is always on main thread, Read this
Remove all weak self. Animations and completion are not retained by self so there is no risk of strong retain cycle. Read this
I have tested this on iOS 12 and everything works fine. See below example.
You just show a snipper of code, I guess there is retain cycle exist so you cannot dismiss current VC. You may use Debug Memory graph in Xcode to check it.
import UIKit
class ViewControllerA: UIViewController {
lazy var dismissButton: UIButton = {
let bt = UIButton(frame: CGRect(x: 0, y: 0, width: view.frame.size.width / 4, height: view.frame.size.width / 6.5))
bt.setTitle("Click", for: .normal)
bt.backgroundColor = .systemRed
bt.layer.cornerRadius = 12
bt.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
return bt
}()
lazy var aView: UIView = {
let v = UIView(frame: CGRect(x: 0, y: 50, width: view.frame.size.width / 2, height: view.frame.size.width / 2))
v.backgroundColor = .brown
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
view.addSubview(dismissButton)
view.addSubview(aView)
dismissButton.center = view.center
aView.center.x = self.view.center.x
}
#objc func buttonTapped() {
UIView.animate(withDuration: 2, animations: {
self.aView.frame.size.width /= 2
self.aView.frame.size.height /= 2
}) { finished in
self.dismiss(animated: true, completion: {
print("dismiss")
})
}
}
}
I am working on a chatbot where the different type of response comes from the server and I display the response using UICollectionView cells in chat screen. Different type of cells presents according to server response. when server response with playing video, I am presenting the cell that contains youtube player. I am using https://github.com/kieuquangloc147/YouTubePlayer-Swift. The issue is when I scroll chat screen (collectionView) youtube player is opening again and again. Sometimes it is blocking all the UI element and stop scrolling. I tried different methods but can't able to resolve it. Here is the code:
PlayerView:
import UIKit
class PlayerView: UIView, YouTubePlayerDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
addYotubePlayer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// youtube player
lazy var youtubePlayer: YouTubePlayerView = {
let viewFrame = UIScreen.main.bounds
let player = YouTubePlayerView(frame: CGRect(x: 0, y: 0, width: viewFrame.width - 16, height: viewFrame.height * 1/3))
player.delegate = self
return player
}()
// used as an overlay to dismiss the youtube player
let blackView = UIView()
// youtube player loader
lazy var playerIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView()
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.hidesWhenStopped = true
return indicator
}()
// shows youtube player
func addYotubePlayer() {
if let window = UIApplication.shared.keyWindow {
blackView.frame = window.frame
self.addSubview(blackView)
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tap.numberOfTapsRequired = 1
tap.cancelsTouchesInView = false
blackView.addGestureRecognizer(tap)
let centerX = UIScreen.main.bounds.size.width / 2
let centerY = UIScreen.main.bounds.size.height / 2
blackView.addSubview(playerIndicator)
playerIndicator.center = CGPoint(x: centerX, y: centerY)
playerIndicator.startAnimating()
blackView.addSubview(youtubePlayer)
youtubePlayer.center = CGPoint(x: centerX, y: centerY)
blackView.alpha = 0
youtubePlayer.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.youtubePlayer.alpha = 1
}, completion: nil)
}
}
func play(_ videoID: String) {
youtubePlayer.loadVideoID(videoID)
}
#objc func handleDismiss() {
blackView.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
UIApplication.shared.keyWindow?.removeFromSuperview()
}
func playerReady(_ videoPlayer: YouTubePlayerView) {
self.playerIndicator.stopAnimating()
}
func playerStateChanged(_ videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
}
func playerQualityChanged(_ videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
}
}
YouTubePlayerCell (Which I present in collectionView wthe hen server responds for video):
import UIKit
class YouTubePlayerCell: ChatMessageCell {
var player: PlayerView = PlayerView(frame: UIScreen.main.bounds)
override func setupViews() {
super.setupViews()
setupCell()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect.zero
}
func loadVideo(with videoID: String) {
player.tag = 24
UIApplication.shared.keyWindow?.addSubview(player)
player.play(videoID)
}
override func prepareForReuse() {
super.prepareForReuse()
player.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
}
}
Here is how I am presenting the YouTubePlayerCell in cellForItemAt method of UICollectionView
let message = messages[indexPath.row]
if message.actionType == ActionType.video_play.rawValue {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell {
self.resignResponders()
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
return cell
}
}
Full Source Code can be found here: https://github.com/imjog/susi_iOS/tree/ytplayer
I can see that in the below code
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
you are calling loadVideo method, which is responsible for showing the player.
So while scrolling you are reusing the cell and it calls loadVideo method and present the player. so the solution is don't start playing the video by default on displaying the cell, provide a play/pause button on the cell video overlay and on clicking the the button start playing the video.
If my analysis is wrong please let me know, what exact issue you have.
Why do you add the player as a subView each time you have to play the video ? My suggestion would be, as you are adding the player view on the whole screen, you can have just one instance of the view and add it just once(may be at the beginning) and keep it hidden. To play the video just unhide the player and load the video.
Instead best practice would be to have a View controller for Youtube Player and present it with the video id each time you need to play and then dismissing when done.
Thanks for your answers. I solve this by this way:
Rather than presenting Player on setting on the cell, I am now adding a thumbnail to the cell and a button on thumbnail view so that whenever the user clicks play button, it opens a new controller (Previously I was presenting in UIWindow) and presenting it as modalPresentationStyle of overFullScreen by using protocol because cell cannot present a ViewController.
Protocol: (In YouTubePlayerCell class)
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
Final YouTubePlayer.swift:
import UIKit
import Kingfisher
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
class YouTubePlayerCell: ChatMessageCell {
weak var delegate: PresentControllerDelegate?
var message: Message? {
didSet {
addThumbnail()
}
}
lazy var thumbnailView: UIImageView = {
let imageView = UIImageView()
imageView.image = ControllerConstants.Images.placeholder
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 15
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var playButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(ControllerConstants.Images.youtubePlayButton, for: .normal)
button.addTarget(self, action: #selector(playVideo), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func setupViews() {
super.setupViews()
setupCell()
prepareForReuse()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect(x: 8, y: 0, width: 208, height: 158)
textBubbleView.layer.borderWidth = 0.2
textBubbleView.backgroundColor = .white
}
override func prepareForReuse() {
super.prepareForReuse()
thumbnailView.image = nil
}
func addThumbnail() {
textBubbleView.addSubview(thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "H:|-4-[v0]-4-|", views: thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: thumbnailView)
self.downloadThumbnail()
self.addPlayButton()
}
func addPlayButton() {
thumbnailView.addSubview(playButton)
playButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
playButton.widthAnchor.constraint(equalToConstant: 44).isActive = true
playButton.centerXAnchor.constraint(equalTo: thumbnailView.centerXAnchor).isActive = true
playButton.centerYAnchor.constraint(equalTo: thumbnailView.centerYAnchor).isActive = true
}
func downloadThumbnail() {
if let videoID = message?.videoData?.identifier {
let thumbnailURLString = "https://img.youtube.com/vi/\(videoID)/default.jpg"
let thumbnailURL = URL(string: thumbnailURLString)
thumbnailView.kf.setImage(with: thumbnailURL, placeholder: ControllerConstants.Images.placeholder, options: nil, progressBlock: nil, completionHandler: nil)
}
}
#objc func playVideo() {
if let videoID = message?.videoData?.identifier {
let playerVC = PlayerViewController(videoID: videoID)
playerVC.modalPresentationStyle = .overFullScreen
delegate?.loadNewScreen(controller: playerVC)
}
}
}
Delegate implementation in CollectionViewController:
extension ChatViewController: PresentControllerDelegate {
func loadNewScreen(controller: UIViewController) {
self.present(controller, animated: true, completion: nil)
}
}
Final source code can be found here: https://github.com/fossasia/susi_iOS/pull/372
I want to put an activity indicator inside a custom class so I can start/stop it from any view controller.
The below code works when starting the activity indicator but not stopping, how can I do this?
static func activityIndicatorFunction(view: UIView, targetVC: UIViewController, animate: Bool) {
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
if animate == false {
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
} else {
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator.backgroundColor = UIColor(red:0.16, green:0.17, blue:0.21, alpha:1)
activityIndicator.layer.cornerRadius = 6
activityIndicator.center = targetVC.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents()
}
}
An example of starting the activity indicator, the animate parameter would be false if I wanted to stop it.
Utils.activityIndicatorFunction(view: view, targetVC: self, animate: true)
This is a perfect candidate for protocol extensions. I recently did this myself.
First create the protocol in a file, say ActivityIndicatorPresenter.swift
/// Used for ViewControllers that need to present an activity indicator when loading data.
public protocol ActivityIndicatorPresenter {
/// The activity indicator
var activityIndicator: UIActivityIndicatorView { get }
/// Show the activity indicator in the view
func showActivityIndicator()
/// Hide the activity indicator in the view
func hideActivityIndicator()
}
Create a protocol extension (in the same file...or a different one)
public extension ActivityIndicatorPresenter where Self: UIViewController {
func showActivityIndicator() {
DispatchQueue.main.async {
self.activityIndicator.activityIndicatorViewStyle = .whiteLarge
self.activityIndicator.frame = CGRect(x: 0, y: 0, width: 80, height: 80) //or whatever size you would like
self.activityIndicator.center = CGPoint(x: self.view.bounds.size.width / 2, y: self.view.bounds.height / 2)
self.view.addSubview(self.activityIndicator)
self.activityIndicator.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.activityIndicator.removeFromSuperview()
}
}
}
Any view controller can then conform to the protocol
class MyViewController: UIViewController, ActivityIndicatorPresenter {
/// Make sure to add the activity indicator
var activityIndicator = UIActivityIndicatorView()
//Suppose you want to load some data from the network in this view controller
override func viewDidLoad() {
super.viewDidLoad()
showActivityIndicator() //Wow you can use this here!!!
getSomeData { data in
//do stuff with data
self.hideActivityIndicator()
}
}
What I suggest is to implement them as two separated methods, also to add them into extension of UIViewController, as follows:
UIViewController extension:
extension UIViewController {
func showActivityIndicator() {
let activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator.backgroundColor = UIColor(red:0.16, green:0.17, blue:0.21, alpha:1)
activityIndicator.layer.cornerRadius = 6
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = .WhiteLarge
activityIndicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents()
activityIndicator.tag = 100 // 100 for example
// before adding it, you need to check if it is already has been added:
for subview in view.subviews {
if subview.tag == 100 {
print("already added")
return
}
}
view.addSubview(activityIndicator)
}
func hideActivityIndicator() {
let activityIndicator = view.viewWithTag(100) as? UIActivityIndicatorView
activityIndicator?.stopAnimating()
// I think you forgot to remove it?
activityIndicator?.removeFromSuperview()
//UIApplication.shared.endIgnoringInteractionEvents()
}
}
I assume that you want to always show/hide the activityIndicator it to ViewController.view, if it is not, you might need let it an activityIndicator of UIView instead of UIViewController.
Usage:
For example, consider that you have two IBActions, the first shows the activity indicator and the other one hides it, they should be like:
#IBAction func show(sender: AnyObject) {
showActivityIndicator()
}
#IBAction func hide(sender: AnyObject) {
hideActivityIndicator()
}
I found it best just to set a 'tag' to the activity indicator and then reference to that when stopping the animation, using one function to hide and one to show as Ahmad suggested. Ahmad F's answer and Guillermo's answer seem good as well.
Show/hide functions inside my Utils.swift file:
static func showActivityIndicator(view: UIView, targetVC: UIViewController) {
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator.backgroundColor = UIColor(red:0.16, green:0.17, blue:0.21, alpha:1)
activityIndicator.layer.cornerRadius = 6
activityIndicator.center = targetVC.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
activityIndicator.tag = 1
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
static func hideActivityIndicator(view: UIView) {
let activityIndicator = view.viewWithTag(1) as? UIActivityIndicatorView
activityIndicator?.stopAnimating()
activityIndicator?.removeFromSuperview()
UIApplication.shared.endIgnoringInteractionEvents()
}
Calling show function:
Utils.showActivityIndicator(view: view, targetVC: self)
Calling hide function:
Utils.hideActivityIndicator(view: view)
When I switch between my tabs it loads some seconds and I want to know that my data is loading. For that I decided to add an activity indicator.
I wrote a little function:
func showActivityIndicator() {
dispatch_async(dispatch_get_main_queue()) {
self.spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
self.spinner.frame = CGRect(x: 0.0, y: 0.0, width: 80.0, height: 80.0)
self.spinner.center = CGPoint(x:self.loadingView.bounds.size.width / 2, y:self.loadingView.bounds.size.height / 2)
self.loadingView.addSubview(self.spinner)
self.view.addSubview(self.loadingView)
self.spinner.startAnimating()
}
}
that will show my indicator. And try to use it when I tapped from my infoController to button:
#IBAction func goToMainFromInfo(sender: AnyObject) {
self.showActivityIndicator()
self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
self.hideActivityIndicator()
}
}
I show it before segue perform and hide after. It doesn't help me. When I did try to use sync:
#IBAction func goToMainFromInfo(sender: AnyObject) {
dispatch_async(dispatch_get_main_queue()) {
self.showActivityIndicator()
self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
self.hideActivityIndicator()
}
}
But it doesn't help too. When I press to tab it opacity becomes 0.5 and I wait while it loading. But I do not see my activity indicator.
What is the problem?
Just try this:
var indicator = UIActivityIndicatorView()
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
And where you want to start animating
indicator.startAnimating()
indicator.backgroundColor = UIColor.whiteColor()
For stop:
indicator.stopAnimating()
indicator.hidesWhenStopped = true
Note: Avoid the calling of start and stop at the same time. Just give some conditions.
SWIFT : 4.2
Just try this:
var indicator = UIActivityIndicatorView()
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
And where you want to start animating
activityIndicator()
indicator.startAnimating()
indicator.backgroundColor = .white
For stop:
indicator.stopAnimating()
indicator.hidesWhenStopped = true
Swift 3.0
// UIView Extension
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
public extension UIView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let activityIndicatorView = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return activityIndicatorView
} else {
let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicatorView.activityIndicatorViewStyle = .gray
activityIndicatorView.color = .gray
activityIndicatorView.center = center
activityIndicatorView.hidesWhenStopped = true
addSubview(activityIndicatorView)
setAssociatedObject(activityIndicatorView, associativeKey: &ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return activityIndicatorView
}
}
set {
addSubview(newValue)
setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// NSObject Extension
public extension NSObject {
func setAssociatedObject(_ value: AnyObject?, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) {
if let valueAsAnyObject = value {
objc_setAssociatedObject(self, associativeKey, valueAsAnyObject, policy)
}
}
func getAssociatedObject(_ associativeKey: UnsafeRawPointer) -> Any? {
guard let valueAsType = objc_getAssociatedObject(self, associativeKey) else {
return nil
}
return valueAsType
}
}
start animation
tableView.activityIndicatorView.startAnimating()
stop animation
tableView.activityIndicatorView.stopAnimating()
You can find more code in Magic
Swift 2+
class ViewController: UITableViewController {
weak var activityIndicatorView: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
tableView.backgroundView = activityIndicatorView
self.activityIndicatorView = activityIndicatorView
activityIndicatorView.startAnimating()
}
...
}
I use two extension methods to add an UIActivityIndicatorView as the backgroundView of the tableview.
extension UITableView {
func showActivityIndicator() {
DispatchQueue.main.async {
let activityView = UIActivityIndicatorView(style: .medium)
self.backgroundView = activityView
activityView.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.backgroundView = nil
}
}
}
You can show/hide it like this.
tableView.showActivityIndicator()
tableView.hideActivityIndicator()
This code can help you :)
let indicator:UIActivityIndicatorView = UIActivityIndicatorView (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()
SWIFT
Place this below your class:
let indicator:UIActivityIndicatorView = UIActivityIndicatorView (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
Place this in your loadView():
indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()
In my case, I am requesting json objects through a func request, so I placed this at the end of that function to remove the activity indicator once the data loads:
self.indicator.stopAnimating()
self.indicator.hidesWhenStopped = true
Another approach, In my code I added an extension for UITableView (Swift 2.3) :
extension UITableView {
func activityIndicator(center: CGPoint = CGPointZero, loadingInProgress: Bool) {
let tag = 12093
if loadingInProgress {
var indicator = UIActivityIndicatorView()
indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
indicator.tag = tag
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
indicator.color = //Your color here
indicator.center = center
indicator.startAnimating()
indicator.hidesWhenStopped = true
self.superview?.addSubview(indicator)
}else {
if let indicator = self.superview?.viewWithTag(tag) as? UIActivityIndicatorView { {
indicator.stopAnimating()
indicator.removeFromSuperview()
}
}
}
}
Note : My tableview is embedded in a UIView (superview)
Update Swift 4.2:
1.call the activityIndicator function on viewDidLoad
eg:
var indicator = UIActivityIndicatorView()
override func viewDidLoad() {
//Initializing the Activity Indicator
activityIndicator()
//Starting the Activity Indicator
indicator.startAnimating()
//Call Your WebService or API
callAPI()
}
Here is the Code For Adding ActivityIndicator as Subview
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.whiteLarge
indicator.color = .red
indicator.center = self.view.center
self.view.addSubview(indicator)
}
2. Do UI related Operations or API Call and stop activity indicator
func callAPI() {
YourModelClass.fetchResult(someParams,
completionHandler: { (response) in
//Do necessary UIUpdates
//And stop Activity Indicator
self.indicator.stopAnimating()
})
}
func setupSpinner(){
spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height:40))
spinner.color = UIColor(Colors.Accent)
self.spinner.center = CGPoint(x:UIScreen.main.bounds.size.width / 2, y:UIScreen.main.bounds.size.height / 2)
self.view.addSubview(spinner)
spinner.hidesWhenStopped = true
}
Using "lazy var". It's better than function
fileprivate lazy var activityIndicatorView: UIActivityIndicatorView = {
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicatorView.hidesWhenStopped = true
// Set Center
var center = self.view.center
if let navigationBarFrame = self.navigationController?.navigationBar.frame {
center.y -= (navigationBarFrame.origin.y + navigationBarFrame.size.height)
}
activityIndicatorView.center = center
self.view.addSubview(activityIndicatorView)
return activityIndicatorView
}()
Just start the spinner anywhere
like this
func requestData() {
// Request something
activityIndicatorView.startAnimating()
}
#brocolli's answer for swift 4.0. You have to use objc_ before getting or setting associated objects. According to the documentation, The APIs of getting and setting the associated object in Swift are:
func objc_getAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>
) -> AnyObject!
func objc_setAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>,
value: AnyObject!,
policy: objc_AssociationPolicy)
Implementation:
import UIKit
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
extension UIView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let activityIndicatorView = objc_getAssociatedObject(self, &ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return activityIndicatorView
} else {
let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicatorView.activityIndicatorViewStyle = .gray
activityIndicatorView.color = .gray
activityIndicatorView.center = center
activityIndicatorView.hidesWhenStopped = true
addSubview(activityIndicatorView)
objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, activityIndicatorView, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return activityIndicatorView
}
}
set {
addSubview(newValue)
objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
In order to place the UIActivityIndicator in foreground, even over the eventual UITableViewController separators, I have adopted this solution.
I have add the UIActivityIndicator programmatically, and add it as a subview of my UINavigationController
var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Code
// .... omissis
// Set activity indicator
activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.color = UIColor.darkGray
activityIndicator.center = tableView.center
activityIndicator.hidesWhenStopped = true
activityIndicator.stopAnimating()
self.navigationController?.view.addSubview(activityIndicator)
}
I have just start & stop it when needed (in my case):
func animateActivityIndicator(_ sender: Any ) {
guard let vc = sender as? UIViewController else { return }
if let v = vc as? MyTableViewController {
if v.activityIndicator.isAnimating {
v.activityIndicator.stopAnimating()
} else {
v.activityIndicator.startAnimating()
}
}
// Others UIViewController or UITableViewController follows...
// all of them exhibits an activityIndicator variable
// implemented programmatically or with the storyboard
}
PS. My environment is Xcode 10.0, iOS 12.0