I'm trying to build a weather app wherein I fetch the weather data using Alamofire. The fetching of data is working fine, however the UILabel which is used to display the fetched values displays the data only for a second after which it disappears. The labels are not part of the tableview, they sit above in UIView of the top part of the screen. The updateMainUI() is the method where I assign labels to the corresponding values to be displayed on screen.
I have gone through solutions and they suggest using Dispatch async, however Im unable to solve this seemingly simple issue.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.dataSource = self
tableView.delegate = self
currentWeather = CurrentWeather()
DispatchQueue.main.async {
self.currentWeather.downloadWeatherDetails {
self.updateMainUI()
}
}
}
The updateMainUI() Method is as follows:
func updateMainUI() {
dateLabel.text = currentWeather.date
print(currentWeather.date)
currentTempLabel.text = "\(currentWeather.currentTemp)"
currentWeatherTypeLabel.text = currentWeather.weatherType
locationLabel.text = currentWeather.cityName
currentWeatherImage.image = UIImage(named: currentWeather.weatherType)}
Instead of this
DispatchQueue.main.async{
self.currentWeather.downloadWeatherDetails {
self.updateMainUI()
}
}
try this
currentWeather.downloadWeatherDetails{
DispatchQueue.main.async{
self.updateMainUI()
}
}
Related
I Have Rest Api Data Display in Tableview , I want different data in same ViewController and reload data on Tableview using Rest api , I want to know how to update my data on same page After click on different categories and Subcategory .
this image for menu of category and sub category
This is my viewController
You Can Do This
var page:Int = 0
var isPageRefreshing:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
YourApi(page1: 0)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(self.mytableview.contentOffset.y >=
(self.mytableview.contentSize.height -
self.mytableview.bounds.size.height)) {
if(isPageRefreshing==false) {
isPageRefreshing=true
print(page)
page = page + 1
YourApi(page1: page)
}
}
}
In your api code
func YourApi(page1 :Int) {
let mypage = String(page1)
let Request: String = String.init(format:"apiName/%#",mypage)
print(Request)
}
I have a UITableView that gets populated by the following firebase database:
"games" : {
"user1" : {
"game1" : {
"currentGame" : "yes",
"gameType" : "Solo",
"opponent" : "Computer"
}
}
}
I load all the games in viewDidLoad, a user can create a new game in another UIViewController, once a user does that and navigates back to the UITableView I want to update the table with the new row. I am trying to do that with the following code:
var firstTimeLoad : Bool = true
override func viewDidLoad() {
super.viewDidLoad()
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef =
Database.database().reference().child("games").child(currentUserID)
gamesRef.observe(.childAdded, with: { (snapshot) in
let game = snapshot
self.games.append(game)
self.tableView.reloadData()
})
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if firstTimeLoad {
firstTimeLoad = false
} else {
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef = Database.database().reference().child("games").child(currentUserID)
gamesRef.observe(.childAdded, with: { (snapshot) in
self.games.append(snapshot)
self.tableView.reloadData()
})
}
}
}
Lets say there is one current game in the data base, when viewDidLoad is run the table displays correctly with one row. However anytime I navigate to another view and navigate back, viewDidAppear is run and for some reason a duplicate game seems to be appended to the games even though no child is added.
The cells are being populated by the games array:
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("GameTableViewCell", owner:
self, options: nil)?.first as! GameTableViewCell
let game = games[indexPath.row]
if let gameDict = game.value as? NSDictionary {
cell.OpponentName.text = gameDict["opponent"] as? String
}
return cell
}
UPDATE:
Thanks to everyone for their answers! It seems like I misunderstood how firebase .childAdded was functioning and I appreciate all your answers trying to help me I think the easiest thing for my app would be to just pull all the data every time the view appears.
From what I can see, the problem here is that every time you push the view controller and go back to the previous one, it creates a new observer and you end up having many observers running at the same time, which is why your data appears to be duplicated.
What you need to do is inside your viewDidDisappear method, add a removeAllObservers to your gameRef like so :
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
guard let currentUserId = Auth.auth().currentUser?.uid else {
return
}
let gamesRef = Database.database().reference().child("games").child(currentUserId)
gamesRef.removeAllObservers()
}
I cannot see all your code here so I am not sure what is happening, but before adding your child added observer, you need to remove all the elements from your array like so :
games.removeAll()
Actually, as per best practices, you should not call your method inside your ViewDidLoad, but instead you should add your observer inside the viewWillAppear method.
I cannot test your code right now but hopefully it should work like that!
Let me know if it doesn't :)
UPDATE:
If you want to initially load all the data, and then pull only the new fresh data that is coming, you could use a combination of the observeSingleEvent(of: .value) and observe(.childAdded) observers like so :
var didFirstLoad = false
gamesRef.child(currentUserId).observe(.childAdded, with: { (snapshot) in
if didFirstLoad {
// add your object to the games array here
}
}
gamesRef.child(currentUserId).observeSingleEvent(of: .value, with: { (snapshot) in
// add the initial data to your games array here
didFirstLoad = true
}
By doing so, the first time it loads the data, .childAdded will not be called because at that time didFirstLoad will be set to false. It will be called only after .observeSingleEvent got called, which is, by its nature, called only once.
Try following code and no need to check for bool , Avoid using bool here its all async methods , it created me an issue in between of my chat app when its database grows
//Remove ref in didLoad
//Remove datasource and delegate from your storyboard and assign it in code so tableView donates for data till your array don't contain any data
//create a global ref
let gamesRef = Database.database().reference()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.games.removeAllObjects()
if let currentUserID = Auth.auth().currentUser?.uid {
gamesRef.child("games").child(currentUserID)observe(.childAdded, with: { (snapshot) in
self.games.append(snapshot)
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
})
gamesRef.removeAllObserver() //will remove ref in disappear itself
//or you can use this linen DidDisappear as per requirement
}
else{
//Control if data not found
}
}
//TableView Delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.games.count == 0{
let emptyLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height))
emptyLabel.text = "No Data found yet"
emptyLabel.textAlignment = .center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = .none
return 0
}
else{
return self.games.count
}
}
observe(.childAdded) is called at first once for each existing child, then one time for each child added.
Since i also encounter a similar issue, assuming you don't want to display duplicate objects, in my opinion the best approach, which is still not listed in the answers up above, is to add an unique id to every object in the database, then, for each object retrieved by the observe(.childAdded) method, check if the array which contains all objects already contains one with that same id. If it already exists in the array, no need to append it and reload the TableView. Of course observe(.childAdded) must also be moved from viewDidLoad() to viewWillAppear(), where it belongs, and the observer must be removed in viewDidDisappear. To check if the array already includes that particular object retrieved, after casting snapshot you can use method yourArray.contains(where: {($0.id == retrievedObject.id)}).
thanks for all help:)! fixed it using iboutlet collection and add properies on viewDidLoad
I'm trying to add properties to keyboard keys like layer.shadowColor or layer.shadowRadius.
I got an error
'Value of type '(UIButton)' -> () has no member 'layer'
how to fix this ?
this is my code keyboardViewController.swift
import UIKit
class KeyboardViewController: UIInputViewController {
var newKeyboardView: UIView!
#IBAction func keyPressed(sender: UIButton) {
}
#IBOutlet var nextKeyboardButton: UIButton!
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
loadInterface()
}
func loadInterface() {
// load the nib file
let keyboardNib = UINib(nibName: "newKeyboard", bundle: nil)
// instantiate the view
newKeyboardView = keyboardNib.instantiateWithOwner(self, options: nil)[0] as! UIView
// add the interface to the main view
view.addSubview(newKeyboardView)
// copy the background color
view.backgroundColor = newKeyboardView.backgroundColor
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
textColor = UIColor.whiteColor()
} else {
textColor = UIColor.blackColor()
}
self.nextKeyboardButton.setTitleColor(textColor, forState: .Normal)
}
}
I think that in order to apply some style to the button, you need an outlet to this button.
Right now, from what I can understand, you are trying to apply styles to the button from the #IBAction to the sender, which is not the proper way to do it.
Try to make an outlet to the button in the view controller and then to apply the styles from within the viewDidLoad method.
I hope this is clear, but if you want a more specific answer you need to show us what you tried, for example pasting the code you have in the view controller
EDIT:
Based on the code you post, the keyboard is a Nib you instantiate from loadInterface(). I don't have a clear vision of the whole thing with only this piece of code, but it seems to me that you are trying to apply some styles to every key button of a keyboard view. Unfortunately this really depends on how the keyboard is implemented, can you provide some more details?
Anyway, from what I see I think you didn't write this code: probably you are following a tutorial or maintaining someone else's code. That's ok, but I suggest you to follow a an introduction course to iOS development with Swift, like the Udacity's one, which is fantastic IMHO (https://www.udacity.com/course/intro-to-ios-app-development-with-swift--ud585)
If you try to format your UIButton with QuartzCore framework, you'll need to import it first:
import QuartzCore
Then you will be able to access those members.
For example (latest swift3 code):
#IBAction func keyPressed(sender: UIButton) {
let button = sender as UIButton!
button?.backgroundColor = UIColor.red
button?.layer.shadowColor = UIColor.black.cgColor
button?.layer.shadowRadius = 1.0
button?.layer.cornerRadius = 4.0
}
In case you need to apply your styles sooner, try to consider to put this code into viewDidLoad or viewDidAppear methods:
self.nextKeyboardButton.backgroundColor = UIColor.red
self.nextKeyboardButton.layer.shadowColor = UIColor.black.cgColor
self.nextKeyboardButton.layer.shadowRadius = 1.0
self.nextKeyboardButton.layer.cornerRadius = 4.0
Seems like you're trying to "add property" not to a button, but rather to a closure which accepts a button as an argument.
Make it like this:
nextKeyboardButton.layer.shadowColor = UIColor.redColor.cgColor
nextKeyboardButton.layer.shadowRadius = 5.0
I'm trying to wrap my head around what could be causing an error I'm seeing. Here's how the general flow looks: when we first hit this feature, we set our tableHeaderView to a custom segmentedcontrol view, then make a service call to fetch some data, when that is complete, we reload the table. This data is all cached, so when the user leaves and comes back, no service call is made, is simply returns back the cached data. However, when coming back, it doesn't seem to recognize that a header view is on the table, and instead places the tableView at the same origin as its header view, so the first cell is covered by the headerView. Here's some relevant methods:
override func viewDidLoad() {
super.viewDidLoad()
self.useAdaptableFeedbackFooter = true
self.tableView.registerReusableCell(DCManagePaymentsTransactionCell.self)
self.tableView.tableHeaderView = self.segmentedControlView
self.segmentedControlView.selectedSegmentIndex = self.selectedSegment.rawValue
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// TODO: Remove once we have other segments implemented
makeServiceCall()
}
private lazy var segmentedControlView: DMSegmentedControlTableHeaderView = {
let view = DMSegmentedControlTableHeaderView.loadableViewFromNib()
let segmentTitles = [DCManagePaymentsString.Summary.localized, DCManagePaymentsString.History.localized, DCManagePaymentsString.Pending.localized]
view.segmentTitles = segmentTitles
view.delegate = self
return view
}()
private func makeServiceCall() {
self.businessService.getData {
(result: DMServiceResult<DCPendingPayments>) -> Void in
switch result {
case .Success (let data):
if data.isHAMode {
self.data.payment = data.pendingPayments.first
self.tableView.dataSource = self.HADataSource
self.tableView.delegate = self.HADataSource
} else {
self.data.pendingPayments = data.pendingPayments
self.tableView.dataSource = self.dataSource
self.tableView.delegate = self.dataSource
}
self.tableView.reloadData()
case .Failure(let errorInfo):
self.navigationController?.handleError(errorInfo, withCompletion: nil)
}
}
}
The only difference I can see is how quickly we eventually call reloadData(). When I set an explicit reloadData() call in viewDidAppear it sets the tableView up correctly, but there's a jerky animation to that.
It seems that I may be setting my table up too fast for it to recognize that there's a header view? Although it doesn't make sense that that's what's causing this. Any ideas or suggestions? I can post more code if necessary.
Edit: I've added two screenshots, first is what the header looks like on first load, second is after you return and it just gets cached data (the Pending Payments is my first cell, and it gets completely covered by the header segmented control in the cached data load):
I wonder how I can show a VC after the remote data has loaded. I am not using a tableView but a normal VC.
My code look like this:
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
loadItemData(id)
}
func loadItemData(aId: Int) {
Service.getItem(aId) { (JSON) -> () in
self.iData = JSON
self.configureData(self.iData)
}
}
func configureData(iData: JSON) {
if let type = iData["item_type"].int {
if let == 1 {
someButton.hidden = true
}
}
if let title = iData["item_title"].string {
titleLabel.text = title
}
}
What happens is that my VC first loads with the button visible and with my text label containing "dummy text" from storyboard, then when the data has loaded the button will hide and the text label will change.
So my question now is how I can hide my VC or show some loading indicator until the data has loaded.
Also worth saying this is the 2nd view. My apps start with a tableView and when you click on a cell you end up in this VC. So I could also load the data when the cell gets clicked then pass it to this VC.
Using the activity indicator is better, here's how to do it
First make sure you add Activity Indicator in your VC
override func viewDidLoad() {
super.viewDidLoad()
self.myActivityIndicator.startAnimating()
loadItemData(id)
}
func loadItemData(aId: Int) {
Service.getItem(aId) { (JSON) -> () in
self.iData = JSON
self.configureData(self.iData)
self.myActivityIndicator.stopAnimating()
}
}
Rather than try to hide the VC (try to load the data and pass it), it will makes the apps feel not responsive, because you have to wait the data loaded and then the VC will show.