I have a UITableViewController that is embedded in a UINavigationController. When I click on a row I am attempting to upload a file. To show the progress of this upload I have decided to use custom popup (another UIViewController) - if anyone has a better idea to show the progress of the upload in this context I am open to it.
The only idea I have to transfer continuous data from one UIViewController to another (if that is possible) is by using a Singleton. My code is below, my issue at the moment is I do not know how to update the progress view even though now it has access to the progress data via the singleton.
class SharedSingleton{
private init(){}
static let shared = SharedSingleton()
var prog: Float = 0
}
UITableViewController:
Using Alamofire to get the fraction of upload completed:
/**TRACK PROGRESS OF UPLOAD**/
upload.uploadProgress { progress in
//print(progress.fractionCompleted)
let mySingleton = SharedSingleton.shared
mySingleton.prog = Float(progress.fractionCompleted)
print("mySingleton.prog: \(mySingleton.prog)")
UIViewController containing popup:
#IBOutlet weak var popUpContainer: UIView!
#IBOutlet weak var uploadStatus: UILabel!
#IBOutlet weak var progressBar: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Make popup have rounded corners
popUpContainer.layer.cornerRadius = 5
popUpContainer.layer.masksToBounds = true
// Call Singleton and assign progress of upload to progress view
// HOW TO UPDATE ??????
let mySingleton = SharedSingleton.shared
progressBar.progress = mySingleton.prog
}
One option would be to use NotificationCenter and in your pop-up view controller subscribe to notifications and in your API callback for the progress, publish a notification.
For example:
UploadNotifications.swift:
import Foundation
extension Notification.Name {
static let UploadProgress = Notification.Name("UploadProgress")
}
UploadProgressViewController.swift (your pop-up):
import UIKit
class UploadProgressViewController: UIViewController {
#IBOutlet weak var progressBar: UIProgressView!
private let progress: Progress = {
let progress = Progress()
progress.completedUnitCount = 0
progress.totalUnitCount = 100
return progress
}()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.uploadDidProgress(_:)), name: .UploadProgress, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
#objc private func uploadDidProgress(_ notification: Notification) {
if let progress = notification.object as? Int64 {
self.progress.completedUnitCount = progress
if progress == 100 {
// dismiss/exit
}
}
}
}
Then in your method with the upload progress callback:
upload.uploadProgress { progress in
NotificationCenter.default.post(name: .SyncDidProgress, object: Int64(progress))
}
Related
I have a view controller with a UIImage that will be sent to a service. I'm trying to load this image from another view so I'm using delegation. However I cant seem to understand why it will not work. I set some messages to print in the console and I see them right before the call to the method but its not setting the variables and I'm not sure why.
The view are linked via a navigation controller and to return I use popViewController and to go to the other view controller I use a simple show segue from the button. The "confirmation" of the selected image is done with a button that is supposed to send the image to the previous view controller.
This is the main controller with the UIImage I want to set from another view:
import UIKit
class MainNewPost: UIViewController{
override var preferredStatusBarStyle: UIStatusBarStyle{
return.lightContent
}
var imageForUpload: UIImage?
var isImageSelected: Bool = false
#IBOutlet weak var newPostBox: TVUIView!
#IBOutlet weak var newPost: UIButton!
#IBAction func sendNewPost(_ sender: Any) {
var imageId = ""
if isImageSelected {
imageId = UUID().uuidString
ImageWS.sendIamge(imageId: imageId, image: imageForUpload!, {()}, error: {(errorMessate) in
print(errorMessate)
})
}
if (newPostBox.text!.isEmpty) {
Util.showMessage(controller: self, message: "Ingrese un texto", seconds: 5)
} else {
PostWS.newPost({() in
Util.showMessage(controller: self, message: "Enviaste un mensaje!", seconds: 3.0)
}, img: imageId, postBody: newPostBox.text!, personId: PersonBE.shared!.personId, posterName: PersonBE.shared!.displayName, error: {(errorMessage) in print(errorMessage)})
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension MainNewPost: NewPostDelegate {
// This is not executing. I'm calling it since I see the console message by the flag and image dont get set
func selectedImage(_ image: UIImage, _ isImageSelected: Bool) {
self.imageForUpload = image
self.isImageSelected = isImageSelected
}
}
This is the view controller where I select the image:
import UIKit
protocol NewPostDelegate {
func selectedImage(_ image: UIImage, _ isImageSelected: Bool)
}
class imageSelectViewController: UIViewController {
var postDelegate: NewPostDelegate?
#IBOutlet weak var imageView: UIImageView!
var imagePicker: ImagePicker!
override func viewDidLoad() {
super.viewDidLoad()
self.imagePicker = ImagePicker(presentationController: self, delegate: self)
}
#IBAction func showImagePicker(_ sender: UIButton) {
self.imagePicker.present(from: sender)
}
override var preferredStatusBarStyle: UIStatusBarStyle{
return.lightContent
}
#IBAction func clickBtnBack(_ sender: Any){
self.navigationController?.popViewController(animated: true)
}
#IBAction func selectImage(_ sender: Any) {
// I see this message printed in the console
// This button is suposed to send the selected image to the previous view controller
print("In selectImage button")
self.postDelegate?.selectedImage(imageView.image!, true)
self.navigationController?.popViewController(animated: true)
}
}
extension imageSelectViewController: ImagePickerDelegate {
func didSelect(image: UIImage?) {
// I see this message printed in the console
print("In didSelect delegate method")
self.imageView.image = image
self.postDelegate?.selectedImage(image!, true)
}
}
Thanks!
It seems that var postDelegate: NewPostDelegate? in your imageSelectViewController instance remains nil. Which means that even if you're calling a delegate method, there is no delegate specified itself.
In order to fix it you should assign your MainNewPost instance reference to var postDelegate: NewPostDelegate? of your destination imageSelectViewController while preparing for segue.
In FourthViewController, I have a slider, which has values ranging from 1 to 1000. The value that is set gets sent via the delegate to PatternViewController, where it should be used to do sth (I put the print for testing purposes).
I've worked with delegates before and it was all ok, checked the code multiple times and multiple answers here on stack, I can't seem to find the issue. Any help would be much appreciated
update: I have added a button so that it would be easier to track along. It turns out that by pressing first time the button, nothing happens. but if I first checkout the PatternViewController, then I go back to FourthViewController and press the button, the delegate gets triggered. anyone got any idea on why is this happening?
FourthViewController
import UIKit
class FourthViewController: UIViewController {
//MARK: Outlets
#IBOutlet var persistenceButton: UIButton!
#IBOutlet var persistenceSlider: UISlider!
#IBOutlet var persistenceLabel: UILabel!
weak var delegate: FourthViewControllerDelegate?
//MARK: Stored Properties - Constants
let userDefaults = UserDefaults.standard
let keyName = "sliderValue"
//MARK: Initializer
override func viewDidLoad() {
super.viewDidLoad()
loadSliderValue()
initialSetUp()
}
//MARK: Actions
#IBAction func handleValueChanged(_ sender: UISlider) {
updateLabel()
persistSliderValue(value: persistenceSlider.value, key: keyName)
}
//MARK: Methods
func updateLabel() {
persistenceLabel.text = String(format: "%.2f", persistenceSlider.value)
}
func persistSliderValue(value: Float, key: String) {
userDefaults.set(value, forKey: key)
}
func loadSliderValue() {
let persistedValue = userDefaults.float(forKey: keyName)
persistenceSlider.value = persistedValue
updateLabel()
}
}
func initialSetUp() {
persistenceButton.addTarget(self, action: #selector(handleButtonPressed), for: .touchUpInside)
}
#objc func handleButtonPressed() {
delegate?.valueChanged(value: persistenceSlider.value)
}
}
PatternViewController
import UIKit
class PatternViewController: UIViewController, FourthViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
func setUp() {
if let tabBar = self.tabBarController, let viewController = tabBar.viewControllers, let fourthViewController = viewController[3] as? FourthViewController {
fourthViewController.delegate = self
}
}
func valueChanged(value: Float) {
print(value)
}
}
It depends upon how you instantiated the tab view controller. If you do it with storyboards, for example, the view controllers for the respective tabs are instantiated lazily, only instantiated as the user taps on them. (This helps reduce latency resulting from instantiating all four of the tabs’ view controllers.)
While you theoretically could go ahead and have the tab bar controller instantiate the four view controllers programmatically up front, rather than just-in-time via the storyboard, I might instead consider specifying a UITabBarControllerDelegate for the tab bar controller. Have the tab bar controller’s delegate method update the relevant tab’s view controller’s model.
Here is an example with two tabs, the first has a slider and the second has a label that displays the slider’s value. In this simplified example, I’ve moved the model object (the value associated with the slider) into the tab bar controller, and it passes it to the second view controller when you select the associated tab.
// TabViewController.swift
import UIKit
class TabBarController: UITabBarController {
var value: Float = 0.5
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
// MARK: - UITabBarControllerDelegate
extension TabViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let viewController = viewController as? SecondViewController else { return }
viewController.value = value
}
}
And
// FirstViewController.swift
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
guard let tabBarController = tabBarController as? TabViewController else { return }
slider.value = tabBarController.value
}
#IBAction func didAdjustSlider(_ sender: UISlider) {
guard let tabBarController = tabBarController as? TabViewController else { return }
tabBarController.value = sender.value
}
}
And
// SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var value: Float = 0 { didSet { updateLabel() } }
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
return formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
updateLabel()
}
func updateLabel() {
label?.text = formatter.string(for: value)
}
}
Probably needless to say, I not only set the base view controller class for the two tab’s view controllers, but also set the base class for the tab bar controller’s storyboard scene to the above TabBarController.
There are 3 child tab view controllers. There are labels as 0 in view controllers. If the number(0) of labels increases in any view controller, I want to increase from the others. How can i do this data transfer.
class tab1Controller: UIViewController {
#IBOutlet weak var countLabel: UILabel!
var count = ""
override func viewDidLoad() {
super.viewDidLoad()
count = countLabel.text!
UserDefaults.standard.set(count, forKey: "count")
UserDefaults.standard.synchronize()
}
class tab2Controller: UIViewController {
#IBOutlet weak var countLabel2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
countLabel2.text = UserDefaults.standard.string(forKey: "count")
}
I did something like this but it didn't work
I think the simplest way in your case is to update the label text in viewWillAppear method.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
countLabel.text = UserDefaults.standard.string(forKey: "count")
}
You also should update the value in UserDefaults every time the number changes.
Do it i viewWillAppear() instead of viewDidLoad().. Because DidLoad() of all controllers called when tabBar construct and show first tab ...
#IBOutlet weak var countLabel2: UILabel!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
countLabel.text = UserDefaults.standard.string(forKey: "count")
}
I assume that your child controllers are created at the same when the tab controller created. In such cases that needed to notify other existing controllers, you must use NotificationCenter.
A notification dispatch mechanism that enables the broadcast of information to registered observers.
extension Notification.Name {
static let didReceiveCountData = Notification.Name("didReceiveCountData")
}
class tab1Controller: UIViewController {
#IBOutlet weak var countLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Listen notifications for name .didReceiveCountData.
// onDidReceiveCountData(_:) will be called when notification received.
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveCountData(_:)), name: .didReceiveCountData, object: nil)
}
// This will be called when the count changes.
#objc func onDidReceiveCountData(_ notification:Notification) {
if let newCount = notification.object as? String {
countLabel.text = newCount
}
}
// Call this when you need to change count and notify other tabs.
private func changeCount(_ newCount: String) {
NotificationCenter.default.post(name: .didReceiveCountData, object: newCount)
}
}
class tab2Controller: UIViewController {
#IBOutlet weak var countLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Listen notifications for name .didReceiveCountData.
// onDidReceiveCountData(_:) will be called when notification received.
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveCountData(_:)), name: .didReceiveCountData, object: nil)
}
// This will be called when the count changes.
#objc func onDidReceiveCountData(_ notification:Notification) {
if let newCount = notification.object as? String {
countLabel.text = newCount
}
}
// Call this when you need to change count and notify other tabs.
private func changeCount(_ newCount: String) {
NotificationCenter.default.post(name: .didReceiveCountData, object: newCount)
}
}
I'm using PanelKit for an iOS app that I'm developing right now.
I have set up a panel, which is a ViewController, and have set up a button in that ViewController. When pressed, I want a picture to be displayed in another ViewController (the main VC that's instantiated when the app is launched). I'm using a delegate for this. However, my delegate function never gets executed. Here's my code:
Delegate:
protocol IMGPickerDelegate: class {
func didFinishPicking(_ imageData: UIImage)
}
Panel ViewController:
class IMGLibraryViewController: UIViewController, PanelContentDelegate {
weak var delegate: IMGPickerDelegate?
#IBAction func fireButton(_ sender: Any) {
let imageSample = UIImage(named: "sample")!
didPickImage(imageDData: imageSample)
}
func didPickImage(imageDData: UIImage) {
delegate?.didFinishPicking(imageDData)
}
Main ViewController:
class ImageDisplayViewController: UIViewController, IMGPickerDelegate {
#IBOutlet weak var imageDisplayViewOutlet: UIImageView!
var imgLibrary = IMGLibraryViewController()
func didFinishPicking(_ imageData: UIImage) {
imageDisplayViewOutlet.image = imageData
}
override func viewDidLoad() {
super.viewDidLoad()
imgLibrary.delegate = self
}
I have a view controller that is responsible for adding a new object, say a new contact. This view controller (AddContactViewController) has the following UIBarButtonItem on a UINavigationBar, which is starts of disabled until enough information is provided to enable it. Then when this button is pressed a method (doneButtonPressed) is called.
The layout is as follows:
class AddContactViewController: UIViewController {
#IBOutlet weak var doneButton: UIBarButtonItem! {
didSet {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
#objc fileprivate func doneButtonPressed() {
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
As this is quite a common thing to have and there's a lot of boiler plate code, I've been working on a protocol AddingHandler but haven't quite worked out how to have UIBarButtonItem as a weak variable which hooks up to a storboard or if this is even the right way to go.
protocol AddingHandler {
var doneButton: UIBarButtonItem? { get set }
func doneButtonPressed()
}
extension protocol where Self: UIViewController {
func configureDoneButton() {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
Any help or comments in making this work would be much appreciated.
The problem How is best to add a weak UIButton to a protocol which can then be hooked up in a story board where UIViewController implements it? As there is a lot of repetitive code here should I wish to have another AddSomethingViewController I was wondering if there was a neater way of only writing this once (in a protocol with an extension) then calling the protocol in any view controller that is adding something new ...
You can simply configure the doneButton in viewDidLoad()
override func viewDidLoad()
{
super.viewDidLoad()
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
Edit 1:
#objc protocol AddingHandler
{
var doneButton: UIBarButtonItem? { get }
#objc func doneButtonPressed()
}
extension AddingHandler where Self: UIViewController
{
func configureDoneButton()
{
doneButton?.isEnabled = false
doneButton?.target = self
doneButton?.action = #selector(doneButtonPressed)
}
}
class AddContactViewController: UIViewController, AddingHandler
{
#IBOutlet weak var doneButton: UIBarButtonItem!
override func viewDidLoad()
{
super.viewDidLoad()
configureDoneButton()
}
func doneButtonPressed()
{
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
I've used ObjC runtime to resolve the issue. Try implementing it at your end and check if it works for you.