I'm still new in iOS development. The idea is I want to put images values from banner (data model). So I need to called func getAllBanner to get all values. Then only called func turnToPage.
The problem is let controller = controllers[index] is
fatal error: Index out of range
When I debug, I noticed that lazy var controllers: [UIViewController]. Why is that ?
Any help is really appreciated.
var images: [UIImage]?
var banner: [Banner]?
// Variable that not create onDidLoad
lazy var controllers: [UIViewController] = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var controllers = [UIViewController]()
if let images = self.images {
for image in images {
let sliderImageVC = storyboard.instantiateViewController(withIdentifier: Storyboard.sliderImageViewController)
controllers.append(sliderImageVC)
}
}
self.pageViewControllerDelegate?.setupPageController(numberOfPages: controllers.count)
return controllers
}()
override func viewDidLoad() {
super.viewDidLoad()
automaticallyAdjustsScrollViewInsets = false
dataSource = self
delegate = self
self.getAllBanner(strAppID: "1", locationid: "2")
self.turnToPage(index: 0)
}
// To allow to turn page
func turnToPage(index: Int) {
let controller = controllers[index]
var direction = UIPageViewControllerNavigationDirection.forward
if let currentVC = viewControllers?.first {
let currentIndex = controllers.index(of: currentVC)
if currentIndex! > index {
direction = .reverse
}
}
self.configureViewDisplaying(viewController: controller)
setViewControllers([controller], direction: direction, animated: true, completion: nil)
}
// Get Banner
func getAllBanner(strAppID: String, locationid: String) {
Banner.getBanner(strAppID: strAppID, locationID: locationid) { [weak self] banner in
guard let `self` = self else {
return
}
self.banner = banner
// Update UI here
if let banners = self.banner {
for banner in banners {
let endPoint = URL(string: banner.ImageURL)
do {
let data = try Data(contentsOf: endPoint!)
self.images = [UIImage(data: data)!]
} catch {
print("Error")
}
}
}
}
}
You can use this snippet.
func getAllBanner(strAppID: String, locationid: String) {
Banner.getBanner(strAppID: strAppID, locationID: locationid) { [weak self] banner in
guard let `self` = self else {
return
}
self.banner = banner
// Update UI here
if let banners = self.banner {
var images: [UIImage] = []
for banner in banners {
let endPoint = URL(string: banner.ImageURL)
do {
let data = try Data(contentsOf: endPoint!)
images.append(UIImage(data: data)!)
} catch {
print("Error")
}
}
self.images = images
// call turnpage method here
}
}
}
Related
Every time when I use pull to refresh, the Active Indicator and the application itself freeze for a moment. I found which method is causing this problem - getTracks(), if I remove it then everything works well (except that the table is not updated), but I can't figure out what's the matter, I've tried many options, but no result. Can anyone help me?
My code:
class LibraryViewController: UIViewController {
private let fetchManager = FetchManager()
private var tracks: [TrackModelProtocol] = []
...
private let refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshTableView), for: .valueChanged)
return refreshControl
}()
...
override func viewDidLoad() {
super.viewDidLoad()
...
getTracks()
setupTableView()
tableView.refreshControl = refreshControl
}
...
private func getTracks() {
guard let tracks = fetchManager.setupTrackModels() else { return }
self.tracks = tracks
}
//pull to refresh
#objc private func refreshTableView(sender: UIRefreshControl) {
getTracks()
tableView.reloadData()
sender.endRefreshing()
}
...
}
...
class FetchManager {
var content: [URL] = []
private var currentDirectory : URL?
private var playerQueue: [AVPlayerItem] = []
private let audioPlayer = AudioPlayer()
private func fetchDataFromDeviceMemory() -> [URL]? {
var content: [URL] = []
if currentDirectory == nil {
currentDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
}
do {
let allURLs = try FileManager.default.contentsOfDirectory(at: currentDirectory!, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants])
content = allURLs.filter{ $0.pathExtension == "mp3" || $0.pathExtension == "aac" || $0.pathExtension == "wav" }
} catch {
content = []
}
return content
}
func setupTrackModels() -> [TrackModelProtocol]? {
guard let content = fetchDataFromDeviceMemory() else { return [] }
self.content = content
playerQueue = audioPlayer.createPlayerQueue(from: content)
var tracks: [TrackModelProtocol] = []
for index in 0..<playerQueue.count {
var artwork: UIImage?
let playerItem = playerQueue[index]
let metadataList = playerItem.asset.metadata
for item in metadataList {
guard let key = item.commonKey?.rawValue, let value = item.value else{
continue
}
switch key {
case "artwork" where value is Data:
artwork = UIImage(data: value as! Data)
default:
continue
}
}
let track = TrackFromDeviceMemory(
fileName: content[index].lastPathComponent,
duration: playerQueue[index].asset.duration.seconds,
artwork: artwork,
url: content[index].absoluteURL)
tracks.append(track)
}
return tracks
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if isSearchActive {
let deletedTrackURL = filteredTracks[indexPath.row].url
print(deletedTrackURL)
do {
try FileManager.default.removeItem(at: deletedTrackURL)
} catch {
print(error.localizedDescription)
}
getTracks()
searchController.isActive = false
// tableView.reloadData()
tableView.delegate?.tableView!(tableView, didSelectRowAt: IndexPath(row: 0, section: indexPath.section))
} else {
let deletedTrackURL = tracks[indexPath.row].url
print(deletedTrackURL)
do {
try FileManager.default.removeItem(at: deletedTrackURL)
} catch {
print(error.localizedDescription)
}
getTracks()
tableView.deleteRows(at: [indexPath], with: .automatic)
// tableView.reloadData()
Instead of returning an array with setupTrackModels, have you tried using a completion handler and perform the fetch on a global queue? Performing the fetch on the main queue itself might be the reason for freeze.
func setupTrackModels(completion: (_ models: [TrackModelProtocol])->Void) {
DispatchQueue.global().async {
guard let content = fetchDataFromDeviceMemory() else {
DispatchQueue.main.async {
completion([])
return
}
}
self.content = content
playerQueue = audioPlayer.createPlayerQueue(from: content)
var tracks: [TrackModelProtocol] = []
for index in 0..<playerQueue.count {
var artwork: UIImage?
let playerItem = playerQueue[index]
let metadataList = playerItem.asset.metadata
for item in metadataList {
guard let key = item.commonKey?.rawValue, let value = item.value else{
continue
}
switch key {
case "artwork" where value is Data:
artwork = UIImage(data: value as! Data)
default:
continue
}
}
let track = TrackFromDeviceMemory(
fileName: content[index].lastPathComponent,
duration: playerQueue[index].asset.duration.seconds,
artwork: artwork,
url: content[index].absoluteURL)
tracks.append(track)
}
DispatchQueue.main.async {
completion(tracks)
}
}
}
At the call site:
fetchManager.setupTrackModels { tracks in
refreshControl.endRefreshing()
self.tracks = tracks
tableView.reloadData()
}
So whenever I add new data, both of my snapshot listeners are executed and the new data gets appended to 'dataArray' which is located outside of my LoadData Function. So what I figured I had to do is reset the data whenever new data get's added so not to duplicate my tableview data. So what I did is I added self.dataArray.removeAll() inside of my top listener, but how would I go about reseting the data for when the other listener gets executed as well?
struct Days: Decodable {
var dow : String
var workouts : [Workouts]
}
struct Workouts: Decodable {
var workout : String
}
var dayCount = 0
var dataArray = [Days]()
//MARK: - viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
vcBackgroundImg()
navConAcc()
picker.delegate = self
picker.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
tableView.tableFooterView = UIView()
Auth.auth().addStateDidChangeListener { (auth, user) in
self.userIdRef = user!.uid
self.rootCollection = Firestore.firestore().collection("/users/\(self.userIdRef)/Days")
self.loadData { (Bool) in
if Bool == true {
self.dayCount = self.dataArray.count
self.tableView.reloadData()
}
}
}
}
//MARK: - Load Data
func loadData(completion: #escaping (Bool) -> ()){
let group = DispatchGroup()
self.rootCollection.addSnapshotListener ({ (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err.localizedDescription)");
}
else {
guard let dayDocument = snapshot?.documents else { return }
self.dataArray.removeAll()
for day in dayDocument {
group.enter()
self.rootCollection.document(day.documentID).collection("Workouts").addSnapshotListener {(snapshot, err) in
var workouts = [Workouts]()
let workoutDocument = snapshot!.documents
try! workoutDocument.forEach({doc in
let tester: Workouts = try doc.decoded()
let workoutString = tester.workout
let newWorkout = Workouts(workout: workoutString)
workouts.append(newWorkout)
})
let dayTitle = day.data()["dow"] as! String
let newDay = Days(dow: dayTitle, workouts: workouts)
self.dataArray.append(newDay)
group.leave()
}
}
}
group.notify(queue: .main){
completion(true)
}
})
}
I am not able to load the documents in chat application in Swift IOS using Firestore database, though able to successfully retrieve the data from the Firestore database, I have added the deinit method as well please assist further to resolve the error, I have added the complete view controller , please help me
Error
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (47) must be equal to the number of rows contained in that section before the update (23), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Code
let kBannerAdUnitID = "ca-app-pub-3940256099942544/2934735716"
#objc(FCViewController)
class FCViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,
UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// Instance variables
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var sendButton: UIButton!
var ref : CollectionReference!
var ref2: DocumentReference!
var messages: [DocumentSnapshot]! = []
var msglength: NSNumber = 10
fileprivate var _refHandle: CollectionReference!
var storageRef: StorageReference!
var remoteConfig: RemoteConfig!
private let db = Firestore.firestore()
private var reference: CollectionReference?
private let storage = Storage.storage().reference()
// private var messages = [Constants.MessageFields]()
//snapshot private var messages: [Constants.MessageFields] = []
private var messageListener: ListenerRegistration?
// var db:Firestore!
#IBOutlet weak var banner: GADBannerView!
#IBOutlet weak var clientTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.clientTable.register(UITableViewCell.self, forCellReuseIdentifier: "tableViewCell")
// clientTable.delegate = self
//clientTable.dataSource = self
//db = Firestore.firestore()
ref = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages")
ref2 = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").document()
configureDatabase()
configureStorage()
configureRemoteConfig()
fetchConfig()
loadAd()
}
deinit {
if let refhandle = _refHandle {
let listener = ref.addSnapshotListener { querySnapshot, error in
}
listener.remove()
}
}
func configureDatabase() {
db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
/* let name = documents.map { $0["name"]!}
let text = documents.map { $0["text"]!}
let photourl = documents.map { $0["photoUrl"]!}
print(name)
print(text)
print(photourl)*/
self.messages.append(contentsOf: documents)
// self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
//self.clientTable.reloadData()
}
}
func configureStorage() {
storageRef = Storage.storage().reference()
}
func configureRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
let remoteConfigSettings = RemoteConfigSettings(developerModeEnabled: true)
remoteConfig.configSettings = remoteConfigSettings
}
func fetchConfig() {
var expirationDuration: Double = 3600
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if self.remoteConfig.configSettings.isDeveloperModeEnabled {
expirationDuration = 0
}
remoteConfig.fetch(withExpirationDuration: expirationDuration) { [weak self] (status, error) in
if status == .success {
print("Config fetched!")
guard let strongSelf = self else { return }
strongSelf.remoteConfig.activateFetched()
let friendlyMsgLength = strongSelf.remoteConfig["friendly_msg_length"]
if friendlyMsgLength.source != .static {
strongSelf.msglength = friendlyMsgLength.numberValue!
print("Friendly msg length config: \(strongSelf.msglength)")
}
} else {
print("Config not fetched")
if let error = error {
print("Error \(error)")
}
}
}
}
#IBAction func didPressFreshConfig(_ sender: AnyObject) {
fetchConfig()
}
#IBAction func didSendMessage(_ sender: UIButton) {
_ = textFieldShouldReturn(textField)
}
#IBAction func didPressCrash(_ sender: AnyObject) {
print("Crash button pressed!")
Crashlytics.sharedInstance().crash()
}
func inviteFinished(withInvitations invitationIds: [String], error: Error?) {
if let error = error {
print("Failed: \(error.localizedDescription)")
} else {
print("Invitations sent")
}
}
func loadAd() {
self.banner.adUnitID = kBannerAdUnitID
self.banner.rootViewController = self
self.banner.load(GADRequest())
}
// UITableViewDataSource protocol methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot: DocumentSnapshot! = self.messages[indexPath.row]
guard let message = messageSnapshot as? [String:String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
if imageURL.hasPrefix("gs://") {
Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
DispatchQueue.main.async {
cell.imageView?.image = UIImage.init(data: data!)
cell.setNeedsLayout()
}
}
} else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage.init(data: data)
}
cell.textLabel?.text = "sent by: \(name)"
} else {
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
}
return cell
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let text = textField.text else { return true }
textField.text = ""
view.endEditing(true)
let data = [Constants.MessageFields.text: text]
sendMessage(withData: data)
return true
}
func sendMessage(withData data: [String: String]) {
var mdata = data
mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
if let photoURL = Auth.auth().currentUser?.photoURL {
mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
}
// Push data to Firebase Database
self.ref.document().setData(mdata, merge: true) { (err) in
if let err = err {
print(err.localizedDescription)
}
print("Successfully set newest city data")
}
}
// MARK: - Image Picker
#IBAction func didTapAddPhoto(_ sender: AnyObject) {
let picker = UIImagePickerController()
picker.delegate = self
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
picker.sourceType = .camera
} else {
picker.sourceType = .photoLibrary
}
present(picker, animated: true, completion:nil)
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion:nil)
guard let uid = Auth.auth().currentUser?.uid else { return }
// if it's a photo from the library, not an image from the camera
if #available(iOS 8.0, *), let referenceURL = info[.originalImage] as? URL {
let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL
let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
guard let strongSelf = self else { return }
strongSelf.storageRef.child(filePath)
.putFile(from: imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
let nsError = error as NSError
print("Error uploading: \(nsError.localizedDescription)")
return
}
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
})
} else {
guard let image = info[.originalImage] as? UIImage else { return }
let imageData = image.jpegData(compressionQuality:0.8)
let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
self.storageRef.child(imagePath)
.putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
guard let strongSelf = self else { return }
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
#IBAction func signOut(_ sender: UIButton) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
dismiss(animated: true, completion: nil)
} catch let signOutError as NSError {
print ("Error signing out: \(signOutError.localizedDescription)")
}
}
func showAlert(withTitle title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title,
message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alert.addAction(dismissAction)
self.present(alert, animated: true, completion: nil)
}
}
}
Edit
perform this block of code on main thread
for doc in documents {
self.messages.append(doc)
self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
}
This should work..
So I have created a model to download data (images, title, desc) using alamofire. But having problem to pass the data and update it in the viewController. If I put the functions in viewController's viewDidLoad then it is working very fine. But I want to use the MVC model. Here is the code for the model:
class PageControllerView {
var _titleName : String!
var _titleDesc : String!
var _image : UIImage!
var titleName : String {
if _titleName == nil {
_titleName = ""
print("titlename is nil")
}
return _titleName
}
var titleDesc : String {
if _titleDesc == nil {
print("tittledesc is nile")
_titleDesc = ""
}
return _titleDesc
}
var image : UIImage {
if _image == nil {
print("Image view is nill")
_image = UIImage(named: "q")
}
return _image
}
func getPageControllerData(_ page : Int) {
Alamofire.request("\(BASE_URL)movie/now_playing?api_key=\(API_KEY)&language=en-US&page=1").responseJSON { (response) in
if let JSON = response.result.value {
if let json = JSON as? Dictionary<String, Any> {
if let results = json["results"] as? [Dictionary<String, Any>] {
if let overview = results[page]["overview"] as? String {
self._titleDesc = overview
}
if let releaseDate = results[page]["release_date"] as? String {
if let title = results[page]["title"] as? String {
let index = releaseDate.index(releaseDate.startIndex, offsetBy: 4)
self._titleName = "\(title) (\(releaseDate.substring(to: index)))"
}
}
if let image_url = results[page]["poster_path"] as? String{
Alamofire.request("\(BASE_URL_IMAGE)\(IMAGE_SIZE)\(image_url)").downloadProgress(closure: { (progress) in
print(progress.fractionCompleted)
}).responseData(completionHandler: { (response) in
print("completed downloading")
if let imageData = response.result.value {
self._image = UIImage(data: imageData)
}
})
}
}
}
}
}
}
}
And this is the viewControllers code (It is working fine but i want to pass the model. The alamofirefuntion is also present in the viewcontroller):
override func viewDidLoad() {
super.viewDidLoad()
print("viewdidload")
getPageControllerData(13)
self.updatePageControllerUI()
}
func updatePageControllerUI() {
pageControllerMovieLabel.text = pageControllerView.titleName
pageControllerSubLabel.text = pageControllerView.titleDesc
pageControlImageView.image = pageControllerView.image
}
func getPageControllerData(_ page : Int) {
Alamofire.request("\(BASE_URL)movie/now_playing?api_key=\(API_KEY)&language=en-US&page=1").responseJSON { (response) in
if let JSON = response.result.value {
if let json = JSON as? Dictionary<String, Any> {
if let results = json["results"] as? [Dictionary<String, Any>] {
if let overview = results[page]["overview"] as? String {
self.pageControllerSubLabel.text = overview
}
if let releaseDate = results[page]["release_date"] as? String {
if let title = results[page]["title"] as? String {
let index = releaseDate.index(releaseDate.startIndex, offsetBy: 4)
self.pageControllerMovieLabel.text = "\(title) (\(releaseDate.substring(to: index)))"
}
}
if let image_url = results[page]["poster_path"] as? String{
Alamofire.request("\(BASE_URL_IMAGE)\(IMAGE_SIZE)\(image_url)").downloadProgress(closure: { (progress) in
print(progress.fractionCompleted)
}).responseData(completionHandler: { (response) in
if let imageData = response.result.value {
self.pageControlImageView.image = UIImage(data: imageData)
}
})
}
}
}
}
}
}
My question is how to pass the model so that i can use like this, by using the PageControllerView object.
override func viewDidLoad() {
super.viewDidLoad()
print("viewdidload")
pageControllerView.getPageControllerData(13)
self.updatePageControllerUI()
}
Now I have checked that this code works but the image is still not shown at firstgo since it has not been downloaded completely but the title and description is showing.
override func viewDidLoad() {
super.viewDidLoad()
print("viewdidload")
pageControllerView.getPageControllerData(4)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.updatePageControllerUI()
}
How do I used a string value from a function in a another class to update an UILabel on my ViewController?
Here is my code:
View controller:
import UIKit
class ViewController: UIViewController, dataEnterdDelegate {
#IBOutlet weak var auaTempLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let weather2 = WeatherService2()
weather2.getWeatherData("Oranjestad,AW")
}
**func userDidEnterInformation(info: NSString)
{
testLabel!.text = info as String
}**
func setLabel2(information: String)
{
auaTempLabel.text = information
}
The other class named WeatherService2 contain the following codes:
**protocol dataEnterdDelegate{
func userDidEnterInformation(info:NSString)
}**
Class WeatherService2{
var currentTempeture:String?
let targetVC = ViewController()
**var delegate: dataEnterdDelegate?**
func getWeatherData(urlString:String)
{
let url = NSURL(string: urlString)!
let sqlQuery = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"\(url)\")"
let endpoint = "https://query.yahooapis.com/v1/public/yql?q=\(sqlQuery)&format=json"
let testString = (String(endpoint))
getData(testString)
}
func getData(request_data: String)
{
let requestString:NSString = request_data.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url_with_data = NSURL(string: requestString as String)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url_with_data){
(data, response, error) in dispatch_async(dispatch_get_main_queue(), {
if data == nil
{
print("Failed loading HTTP link")
}else{
self.setLabel(data!)
}
})
}
task.resume()
}
func setLabel(weatherData:NSData)
{
enum JSONErrors: ErrorType
{
case UserError
case jsonError
}
do{
let jsonResults = try NSJSONSerialization.JSONObjectWithData(weatherData, options: .AllowFragments)
if let city = jsonResults["query"] as? NSDictionary
{
if let name = city["results"] as? NSDictionary
{
if let channel = name["channel"] as? NSDictionary
{
if let item = channel["item"] as? NSDictionary
{
if let condition = item["condition"] as? NSDictionary
{
if let temp = condition["temp"] as? String
{
setTemp(temp)
**delegate!.userDidEnterInformation(temp)**
}
}
}
}
}
}
}
catch {
print("Failed to load JSON Object")
}
}
func setTemp(tempeture:String)
{
self.currentTempeture = tempeture
}
func getTemp() ->String
{
return self.currentTempeture!
}
}
The code runs fine and everything but I get an error "Fatal error: unexpectedly found nil while unwrapping an Optional value" when I try to update the UILabel in my ViewController.
When I used the print("The return value is: "+information) in the view controller class it print the return value correctly.
This is the reason I'm confused right now because I don't know why I still getting the "Fatal error: unexpectedly found nil while unwrapping an Optional value" when trying to use this value to update my UILabel.
Can anyone help me with this problem?
Thanks in advance
For that you have to create delegate method.
In viewController you create delegate method and call it from where you get response and set viewController.delegate = self
I could not explain more you have to search for that and it will works 100% .
All the best.
I manage to fix this issue by doing the following:
I create the following class
- Item
- Condition
- Channel
These classes implement the JSONPopulator protocol.
The JSONPopulator protocol:
protocol JSONPopulator
{
func populate(data:AnyObject)
}
Item class:
class Item: JSONPopulator
{
var condition:Condition?
func getCondition() ->Condition
{
return condition!
}
func populate(data: AnyObject)
{
condition = Condition()
condition?.populate(data)
}
}
Condition class:
class Condition:JSONPopulator
{
var arubaTemp:String?
var channel:NSDictionary!
func getArubaTemp()->String
{
return arubaTemp!
}
func getBonaireTemp() ->String
{
return bonaireTemp!
}
func getCuracaoTemp()->String
{
return curacaoTemp!
}
func populate(data: AnyObject)
{
if let query = data["query"] as? NSDictionary
{
if let results = query["results"] as? NSDictionary
{
if let channel = results["channel"] as? NSDictionary
{
self.channel = channel
if let location = channel["location"] as? NSDictionary
{
if let city = location["city"] as? String
{
if city.containsString("Oranjestad")
{
switch city
{
case "Oranjestad":
arubaTemp = getTemp()
print(arubaTemp)
default:
break
}
}
}
}
}
}
}
}
func getTemp() ->String
{
var temp:String?
if let item = self.channel["item"] as? NSDictionary
{
if let condition = item["condition"] as? NSDictionary
{
if let tempeture = condition["temp"] as? String
{
print(tempeture)
temp = tempeture
}
}
}
print(temp)
return temp!
}
}
Channel class:
class Channel: JSONPopulator
{
var item:Item?
var unit:Unit?
var request_city:String?
func setRequestCity(request_city:String)
{
self.request_city = request_city
}
func getRequestCity() ->String
{
return request_city!
}
func getItem() -> Item
{
return item!
}
func getUnit() -> Unit
{
return unit!
}
func populate(data: AnyObject)
{
item = Item()
item?.populate(data)
}
}
The WeatherService class that handles the function of parsing the JSON object. This class implement a WeatherServiceCallBack protocol.
The WeatherServiceCallBack protocol:
protocol WeatherServiceCallBack
{
func arubaWeatherServiceService( channel:Channel)
func arubaWeatherServiceFailure()
}
WeatherService class:
class WeatherService
{
var weatherServiceCallBack:WeatherServiceCallBack
var requestCity:String?
init(weatherServiceCallBack: WeatherServiceCallBack)
{
self.weatherServiceCallBack = weatherServiceCallBack
}
internal func checkCity(city:String)
{
switch (city)
{
case "Oranjestad,AW":
requestCity = city
getWeatherData(requestCity!)
default:
break
}
}
func getWeatherData(urlString:String)
{
let url = NSURL(string: urlString)!
let sqlQuery = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"\(url)\")"
let endpoint = "https://query.yahooapis.com/v1/public/yql?q=\(sqlQuery)&format=json"
let testString = (String(endpoint)
executeTask(testString)
}
func executeTask(request_data: String)
{
let requestString:NSString = request_data.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url_with_data = NSURL(string: requestString as String)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url_with_data){
(data, response, error) in dispatch_async(dispatch_get_main_queue(), {
if data == nil
{
print("Failed loading HTTP link")
}else{
self.onPost(data!)
}
})
}
task.resume()
}
func onPost(data:NSData)
{
enum JSONErrors: ErrorType
{
case UserError
case jsonError
}
do{
let jsonResults = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
print(jsonResults)
if let city = jsonResults["query"] as? NSDictionary
{
if let name = city["count"] as? Int
{
if name == 0
{
weatherServiceCallBack.arubaWeatherServiceFailure()
}
}
}
if let requestCity_check = jsonResults["query"] as? NSDictionary
{
if let results = requestCity_check["results"] as? NSDictionary
{
if let channel = results["channel"] as? NSDictionary
{
if let location = channel["location"] as? NSDictionary
{
if let city = location["city"] as? String
{
requestCity = city
let channel = Channel()
channel.setRequestCity(requestCity!)
channel.populate(jsonResults)
weatherServiceCallBack.arubaWeatherServiceService(channel)
}
}
}
}
}
}catch {
print("Failed to load JSON Object")
}
}
}
In the ViewController class (I add some animation to the UILabel so it can flip from Fahrenheit to Celsius):
class ViewController: UIViewController, WeatherServiceCallBack
{
var weather:WeatherService?
var aua_Tempeture_in_F:String?
var aua_Tempeture_in_C:String?
var timer = NSTimer()
#IBOutlet var aua_Temp_Label: UILabel!
let animationDuration: NSTimeInterval = 0.35
let switchingInterval: NSTimeInterval = 5 //10
override func viewDidLoad() {
super.viewDidLoad()
weather = WeatherService(weatherServiceCallBack: self)
weather?.checkCity("Oranjestad,AW")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func animateTemptext()
{
self.timer = NSTimer.scheduledTimerWithTimeInterval(7.0, target: self, selector: Selector("tempConvertionTextSwitch"), userInfo: nil, repeats: true)
}
func setTempinCelsius(temp_string:String)
{
aua_Tempeture_in_F = "\(temp_string)°F"
let convertedString = convertFahrenheittoCelsius(temp_string)
aua_Tempeture_in_C = "\(convertedString)°C"
aua_Temp_Label.text = aua_Tempeture_in_C
animateTemptext()
}
func convertFahrenheittoCelsius(currentTemp:String) ->String
{
let tempTocelsius = (String(((Int(currentTemp)! - 32) * 5)/9))
return tempTocelsius
}
#objc func tempConvertionTextSwitch()
{
CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock{
let delay = dispatch_time(DISPATCH_TIME_NOW,Int64(self.switchingInterval * NSTimeInterval(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue())
{
}
}
let transition = CATransition()
transition.type = kCATransitionFade
if aua_Temp_Label.text == aua_Tempeture_in_F
{
aua_Temp_Label.text = aua_Tempeture_in_C
}else if aua_Temp_Label.text == aua_Tempeture_in_C
{
aua_Temp_Label.text = aua_Tempeture_in_F
}else if aua_Temp_Label == ""
{
aua_Temp_Label.text = aua_Tempeture_in_C
}
aua_Temp_Label.layer.addAnimation(transition, forKey: kCATransition)
CATransaction.commit()
}
func arubaWeatherServiceFailure() {
}
func arubaWeatherServiceService(channel: Channel)
{
let requested_city = channel.getRequestCity()
let items = channel.getItem()
let aua_Temp = items.getCondition().getArubaTemp()
setTempinCelsius(aua_Temp)
}
}
Reference:
iOS 8 Swift Programming Cookbook Solutions Examples for iOS Apps book
iOS 8 Programming Fundamentals with Swift Swift, Xcode, and Cocoa Basics book
Hope it help the once that had the same problem