I am working on an open source application using Xcode 9.3.1 Swift 4.
There is a play button. When the user clicks on the play button the audio file downloads automatically before it starts playing. I want to add an alert message that alerts the user, shows the file size, and lets him accept or not to proceed with the download.
Here my audiobarview.xib code:
import GenericDataSources
import QueuePlayer
import UIKit
internal protocol AdvancedAudioOptionsViewControllerDelegate : AnyObject {
internal func advancedAudioOptionsViewController(_ controller: AdvancedAudioOptionsViewController, finishedWith options: AdvancedAudioOptions)
}
internal class AdvancedAudioOptionsViewController : UIViewController, UIGestureRecognizerDelegate {
weak internal var delegate: AdvancedAudioOptionsViewControllerDelegate?
#IBOutlet weak internal var tableView: UITableView!
#IBOutlet weak internal var contentView: UIView!
#IBOutlet weak internal var bottomConstraint: NSLayoutConstraint!
#IBOutlet weak internal var navigationBar: UINavigationBar!
lazy internal var playButton: UIButton { get set }
internal init(options: AdvancedAudioOptions)
required internal init?(coder aDecoder: NSCoder)
override internal func viewDidLoad()
override internal func viewDidAppear(_ animated: Bool)
override internal func viewDidLayoutSubviews()
#IBAction internal func playButtonTapped(_ sender: Any)
#IBAction internal func dismissView(_ sender: Any)
internal func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
}
extension UIView {
internal func round(corners: UIRectCorner, radius: CGFloat)
}
Here is the audiofilesdownloader.swift code:
import BatchDownloader
import PromiseKit
class AudioFilesDownloader {
let audioFileList: QariAudioFileListRetrieval
let downloader: DownloadManager
let ayahDownloader: AnyInteractor<AyahsAudioDownloadRequest, DownloadBatchResponse>
private var response: DownloadBatchResponse?
init(audioFileList: QariAudioFileListRetrieval,
downloader: DownloadManager,
ayahDownloader: AnyInteractor<AyahsAudioDownloadRequest, DownloadBatchResponse>) {
self.audioFileList = audioFileList
self.downloader = downloader
self.ayahDownloader = ayahDownloader
}
func cancel() {
response?.cancel()
response = nil
}
func needsToDownloadFiles(qari: Qari, range: VerseRange) -> Bool {
let files = filesForQari(qari, range: range)
return !files.filter { !FileManager.documentsURL.appendingPathComponent($0.destinationPath).isReachable }.isEmpty
}
func getCurrentDownloadResponse() -> Promise<DownloadBatchResponse?> {
if let response = response {
return Promise(value: response)
} else {
return downloader.getOnGoingDownloads().then { batches -> DownloadBatchResponse? in
let downloading = batches.first { $0.isAudio }
self.createRequestWithDownloads(downloading)
return self.response
}
}
}
func download(qari: Qari, range: VerseRange) -> Promise<DownloadBatchResponse?> {
return ayahDownloader
.execute(AyahsAudioDownloadRequest(range: range, qari: qari))
.then(on: .main) { responses -> DownloadBatchResponse? in
// wrap the requests
self.createRequestWithDownloads(responses)
return self.response
}
}
private func createRequestWithDownloads(_ batch: DownloadBatchResponse?) {
guard let batch = batch else { return }
response = batch
response?.promise.always { [weak self] in
self?.response = nil
}
}
func filesForQari(_ qari: Qari, range: VerseRange) -> [DownloadRequest] {
return audioFileList.get(for: qari, range: range).map {
DownloadRequest(url: $0.remote, resumePath: $0.local.stringByAppendingPath(Files.downloadResumeDataExtension), destinationPath: $0.local)
}
}
}
Assuming you have the filesize as a variable filesize
Swift 4:
let alert = UIAlertController(title: "Proceed with download?", message: "File size: \(filesize)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: {(alert: UIAlertAction!) in
// Leave empty, dismisses the alert
}))
alert.addAction(UIAlertAction(title: "Download Song", style: .default, handler: {(alert: UIAlertAction!) in
// Code to download song
}))
self.present(alert, animated: true, completion: nil)
Related
well, im using 2 VCs, one with a textField where the user inputs the CityName, and another VC where it takes care of all the UI elements(like the temp, cityname, etc..), now I use also a NetWorkManager to take care of all the networking&JSON stuff.
the problem is im trying to transfer the data from the NetWorkManager to VC1 but for some reason the delegate aint working :( - basically the road should be like this : VC2 -> NetWorkManager -> VC1.
Here's my Code:
import Foundation
protocol NetworkManagerDelegate {
func didUpdateWeather(weather: WeatherModel)
}
struct NetworkManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=2da9980c9a43e21c2cdb1f28316d151d&units=metric"
var delegate: NetworkManagerDelegate?
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
if error != nil {
print(error!)
}
if let safeData = data {
if let weather = self.parseJSON(weatherData: safeData) {
print("Im not nil")
self.delegate?.didUpdateWeather(weather: weather)
}
}
}
task.resume()
}
}
func parseJSON(weatherData: Data) -> WeatherModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherDataModel.self, from: weatherData)
let id = decodedData.weather[0].id
let cityName = decodedData.name
let temp = decodedData.main.temp
let weather = WeatherModel(conditionId: id, cityName: cityName ,temperatrue: temp)
print("Temp is: \(weather.temperatrueString)")
return weather
} catch {
print(error)
return nil
}
}
}
VC2:
import UIKit
import Foundation
class WeatherByCityController: UIViewController, UITextFieldDelegate {
// func didUpdateWeather(weather: WeatherModel) {
// print("Hi")
// }
//
#IBOutlet weak var cityTextField: UITextField!
#IBOutlet weak var updateWeatherBtn: UIButton!
var netWorkManager = NetworkManager()
override func viewDidLoad() {
super.viewDidLoad()
// netWorkManager.delegate = self
cityTextField.delegate = self
}
#IBAction func closeButtonTapped(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
#IBAction func updateWeatherByCityTapped(_ sender: UIButton) {
//Calling delegate to update the City:
//Dismiss the VC:
dismiss(animated: true, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
cityTextField.endEditing(true)
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let city = cityTextField.text {
netWorkManager.fetchWeather(cityName: city)
}
cityTextField.text = ""
}
}
VC1:
import UIKit
import Foundation
import CoreLocation
class WeatherScreen: UIViewController,NetworkManagerDelegate {
//Objects outlets:
#IBOutlet weak var conditionIcon: UIImageView!
#IBOutlet weak var tempLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
//TableView Outlet:
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var segeControl: UISegmentedControl!
var models = [DailyWeatherEntry]()
var hourlyModels = [HourlyWeatherEntry]()
var netWorkManager = NetworkManager()
override func viewDidLoad() {
netWorkManager.delegate = self
tableView.register(HourlyTableViewCell.nib(), forCellReuseIdentifier: HourlyTableViewCell.identifier)
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
// Load things once the view will appear
}
#IBAction func locationBtnTapped(_ sender: UIButton) {
//Asking the user for a permission for using his location:
}
func didUpdateWeather(weather: WeatherModel) {
print("Hi")
}
}
extension WeatherScreen: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//creating the cell:
let cell = tableView.dequeueReusableCell(withIdentifier: "weatherCell", for: indexPath) as! HourlyTableViewCell
//Cell Configure:
cell.textLabel!.font = UIFont.systemFont(ofSize: 10)
return cell
}
}
Where is the code for the VC1 ? Without the code for VC 1 it is hard to give an answer. However i'll try to answer as this might be the scenario.
So basically what you are trying to do is make a network call from the VC2 and whatever the response it should be updated in VC1 which is already active somewhere else. Here you just have to set the delegate of the NetworkManger to the VC1 instance. So you have to get the instance of VC1 in VC2.
var netWorkManager = NetworkManager()
//Get this instance in your code
var vc1: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Here you will be setting the delegate
// to VC1 where you will be having the delegate methods
netWorkManager.delegate = vc1
cityTextField.delegate = self
}
While this method works I would not recommend using the network manager in such a way. Try to use completion handlers instead of delegates to get the data and then pass that values between the view controllers.
Edited:
Pass completion like this in the Network Manager performRequest function.
func performRequest(urlString: String, completion: #escaping (Bool, String?, Error?) -> Void) {
guard let url = URL(string: urlString) else {
completion(false, nil, NSError(domain: "URLString is not a valid URL", code: 100, userInfo: nil))
return
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
guard let safeData = data, let weather = self.parseJSON(weatherData: safeData) else {
print("Empty data or JSON parse error")
completion(false, nil, error)
}
print("Im not nil")
completion(true, weather, nil)
}
task.resume()
}
And call the api request in the VC2.
#IBAction func updateWeatherByCityTapped(_ sender: UIButton) {
//Calling the api request. Pass your url string here
self. netWorkManager.performRequest(urlString: "") { (success, weather, error) in
guard success else {
print(error as Any)
return
}
// Here you have got the weather data.
// Don't know what is weather model. so simply passing the weather string.
self.delegate.didUpdateWeather(weather: weather)
//Dismiss the VC:
dismiss(animated: true, completion: nil)
}
}
Here adopt the NetworkManagerDelegate to VC1 and before presenting the VC2 set the delegate to VC1. Or if you are not presenting the VC2 from VC1 then use UserNotifications to send the weather data to the VC1.
This line of code var netWorkManager = NetworkManager() creates a new instance of network manager each time it is invoked.
In your case, the network manager in VC1 will not get called when the network manager you created in VC2 receives a network response. They are two separate entities.
There a few things you can think about:
If I understand your scenario correctly, your VC2 is used to get a city name from the user. Does VC2 really need to make a network call? You could restrict VC2 to only fetch the city name.
Make the networkManager a singleton. You can then call it from multiple places in your code. The networkManger can have method to 'fetch' and it can take in a completionHandler (as #Raja Vijaya kumar) had suggested.
Hey guys I am trying to retrieve and display a cloud anchor from my IOS app and never receive a delegate callback from resolve cloud anchor here is my view controller :
class ShowBooViewController: UIViewController,GARSessionDelegate, ARSessionDelegate {
var garSession : GARSession? = nil
var arScene : SCNScene? = nil
#IBOutlet weak var arSceneView: ARSCNView!
#IBAction func showBoo(_ sender: UIButton) {
let ac = UIAlertController(title: "Enter answer", message: nil, preferredStyle: .alert)
ac.addTextField()
let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in
//let answer = ac.textFields![0]
// do something interesting with "answer" here
}
ac.addAction(submitAction)
present(ac, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.garSession = GARSession.init(apiKey: "RedactedCuzSuperSecret",bundleIdentifier: nil)
self.garSession?.delegate = self
do {
try self.garSession!.resolveCloudAnchor(withIdentifier: "bgfbgfbgf")
} catch {
print("Failed")
}
} catch {
print("Failed")
print(error)
}
}
func session(_ session: GARSession, didResolve anchor: GARAnchor) {
print("session")
}
func session(_ session: GARSession, didFailToResolve anchor: GARAnchor) {
print("Session")
}
func session(_ session: GARSession, didHostAnchor anchor: GARAnchor) {
print("Session")
}
func session(_ session: GARSession, didFailToHostAnchor anchor: GARAnchor) {
print("Session")
}
}
The api key was changed to not post here but I have the actual api key in view controller, when using Xcode inspector I see that the delegate is assigned to the view controller I just do not get the session callbacks.
I've successfully parsed an result from a SOAP WebServices request I made, by sending in a userName and password.
But now I need to validate this login function and if true segue to a new view programmatically:
let menuPageView = (self.storyboard?.instantiateViewController(withIdentifier: "MenuCentral"))!
self.present(menuPageView, animated: true, completion: nil)
The problem is I don't know how or where to add such validation.
class LoginCentralViewController: UIViewController, SOAPServiceProtocol {
var chave = ChaveWebService().chave()
var soapService : SOAPService?
var resultadoLoginCentral : [LoginCentralModel]!
#IBOutlet weak var txtUsuarioOUTLET: UITextField!
#IBOutlet weak var txtSenhaOUTLET: UITextField!
#IBOutlet weak var btnAcessarOUTLET: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
soapService = SOAPService(delegate: self)
print(chave)
}
#IBAction func btnAcessarACTION(_ sender: Any) {
soapService?.loginCentral(userName: txtUsuarioOUTLET.text!, password: txtSenhaOUTLET.text!, methodName: nomeServico)
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didSuccessRequest(results: XMLIndexer, requestName: String) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
switch requestName {
case nomeServico:
do{
resultadoLoginCentral = try LoginCentralModel.realizarLoginCentral(results: results)
} catch let error as XMLParseError{
print(error.description)
return
} catch {
print(error)
return
}
print("codigoCliente = ", resultadoLoginCentral[0].codigoCliente)
print("permissoes = " , resultadoLoginCentral[0].permissoes)
break
default:
break
}
}
func didFailRequest(err: String, requestName: String) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
switch requestName {
case nomeServico:
return
default:
break
}
}
func showAlert() {
let loginAlert = UIAlertController(title: "Central do Assinante", message: "Login/Senha inválidos", preferredStyle: .alert)
let acaoDefault = UIAlertAction(title: "OK", style: .destructive, handler: nil)
loginAlert.addAction(acaoDefault)
present(loginAlert, animated: true, completion: nil)
}
}
You can put your validation code here
do{
resultadoLoginCentral = try LoginCentralModel.realizarLoginCentral(results: results)
//Put here code
// we need to call this in main thread
DispatchQueue.main.sync {
if resultadoLoginCentral.codigoCliente.characters.count > 0 && resultadoLoginCentral.permissoes.characters.count > 0{{
// Login process
}else{
//Show Alert
}
}
}
Hope this helps
I am trying to send a mail from the collection view cell controller, but I get error. Here is full code:
import Foundation
import UIKit
import MessageUI
import Parse
import Bolts
class CollectionViewCell: UICollectionViewCell, MFMailComposeViewControllerDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var imageText: UILabel!
#IBOutlet weak var uploadedTimeLabel: UILabel!
#IBOutlet weak var currentUserLabel: UILabel!
#IBOutlet weak var flagContentButton: UIButton!
var deviceID = [String]()
var parseObject:PFObject?
#IBAction func buttonClick(sender: AnyObject) {
println(deviceID)
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self // Extremely important to set the --mailComposeDelegate-- property, NOT the --delegate-- property
mailComposerVC.setToRecipients(["test#email.com"])
mailComposerVC.setSubject("Test subject")
mailComposerVC.setMessageBody("Test mail!", isHTML: false)
return mailComposerVC
}
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// MARK: MFMailComposeViewControllerDelegate
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
}
Error line:
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
Error code:
'CollectionViewCell' does not have a member named
'presentViewController'
Any suggestions what to do here?
You could use completion blocks or a delegate to notify the view controller, like below. You would set either delegate or notifyViewController on the cell in your cellForItemAtIndexPath. This way, you can tell each cell which controller you want to handle presenting the mail controller and the controller can decide how to do it.
protocol CollectionViewCellDelegate {
func notifyViewController(mailViewController: MFMailComposeViewController)
}
class CollectionViewCell: ... {
//...
var delegate: CollectionViewCellDelegate?
// you could use this completion block property in place of a delegate
var notifyViewController: ((MFMailComposeViewController) -> Void)?
//...
#IBAction func buttonClick(sender: AnyObject) {
println(deviceID)
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
delegate?.notifyViewController(mailComposeViewController)
// OR
notifyViewController?(mailComposeViewController)
} else {
self.showSendMailErrorAlert()
}
}
//...
}
I'm following a tutorial called How to Build a Simple iOS Chat App on Youtube which made by a channel called Code With Chris. I followed all his type and I got the following error in Xcode:
/Users/David/Documents/360Drive/Xcode/Try/Learn With Chris/ChatApp/ChatApp/ChatApp/ViewController.swift:53:15: Cannot invoke 'findObjectsInBackgroundWithBlock' with an argument list of type '(([AnyObject]!, NSError!) -> Void)'
Here's my code:
//
// ViewController.swift
// ChatApp
//
// Created by David Chen on 15/4/12.
// Copyright (c) 2015 cwsoft. All rights reserved.
//
import UIKit
import Parse
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
var messagesArray:[String] = [String]()
#IBOutlet weak var MessageTableView: UITableView!
#IBOutlet weak var ButtonSend: UIButton!
#IBOutlet weak var DockViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var MessageTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//Set delegate
self.MessageTableView.delegate = self
self.MessageTableView.dataSource = self
self.MessageTextField.delegate = self
let tapGesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tableViewTapped")
self.MessageTableView.addGestureRecognizer(tapGesture)
self.messagesArray.append("Test 1")
self.messagesArray.append("Test 2")
self.messagesArray.append("Test 3")
}
#IBAction func ButtonSendPressed(sender: UIButton) {
self.MessageTextField.endEditing(true)
var newMessageObject:PFObject = PFObject(className: "Message")
newMessageObject["Text"] = self.MessageTextField.text
newMessageObject.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success == true) {
NSLog("Success")
} else {
NSLog("Error")
}
}
}
func retrieveMessages() {
var query:PFQuery = PFQuery(className: "Messages")
query.findObjectsInBackgroundWithBlock {
(object: [AnyObject]!, error: NSError!) -> Void in
self.messagesArray = [String]()
for messageObject in objects {
let messageText:String? = (messageObject as PFObject)["Text"] as? String
if messagetext != nil {
self.messagesArray.append(messageText!)
}
}
}
self.MessageTableView.reloadData()
}
func tableViewTapped() {
self.MessageTextField.endEditing(true)
}
//MARK : TextField Delegage Methods
func textFieldDidBeginEditing(textField: UITextField) {
self.view.layoutIfNeeded()
UIView.animateWithDuration(0.5, animations: {
self.DockViewHeightConstraint.constant = 320
self.view.layoutIfNeeded()
}, completion: nil)
}
func textFieldDidEndEditing(textField: UITextField) {
self.view.layoutIfNeeded()
UIView.animateWithDuration(0.5, animations: {
self.DockViewHeightConstraint.constant = 60
self.view.layoutIfNeeded()
}, completion: nil)
}
//MARK : Table View Delegate Methods
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.MessageTableView.dequeueReusableCellWithIdentifier("MessageCell") as! UITableViewCell
cell.textLabel?.text = self.messagesArray[indexPath.row]
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messagesArray.count
}
}
Here's the link of the tutorial
link
Thanks for any helpful suggestions :)
There have been many changes with the last xCode update. I can not really explain why exactly some changes need to be done. You better read the changeLogs! So will I later on :)
In the meantime try this:
func retrieveMessages() {
var query:PFQuery = PFQuery(className: "Messages")
query.findObjectsInBackgroundWithBlock {
(object, error) -> Void in
self.messagesArray = [String]()
for messageObject in object! {
let messageText:String? = (messageObject as! PFObject)["Text"] as? String
if messageText != nil {
self.messagesArray.append(messageText!)
}
}
}
self.MessageTableView.reloadData()
}
It seems that in some cases you don't need to specify the type anymore. In other cases you need to though. In this case, there is no need but you need unwrap your object array.