How to pass Item from initial (one time) first time view controller to main view controller and save that data using core data - ios

I have been working on this issue for two days now and sadly I cannot figure out the issue to my problem. I'm trying to take one item from my initial one time view controller and send that to my main view controller where it will be saved within the main view controller and will appear upon that controller when reloading the app.
Here is my app delegate code for the "first time" view controller
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if UserDefaults.standard.bool(forKey: "firstTimer") {
let storyBoard = UIStoryboard.init(name: "Main", bundle: nil)
let mainView = storyBoard.instantiateViewController(withIdentifier: "MainViewControllerID")
let nav = UINavigationController(rootViewController: mainView)
nav.navigationBar.isHidden = true
self.window?.rootViewController = nav
}
return true
}
containers and saveContext are default
import UIKit
import CoreData
class FirstTimeViewController: UIViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
private var player = [Player]()
#IBOutlet weak var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// This View Controller will only be used once upon the first time the app is being used.
// MARK: Make func that prepares for segue on initial opening of app
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMainViewController" {
let mainViewController = segue.destination as! UINavigationController
let destination = mainViewController.topViewController as! MainViewController
if let newPlayer = self.nameTextField.text{
destination.name.name = newPlayer
destination.playerData.name = newPlayer
saveItems()
}
}
}
#IBAction func continueButtonPressed(_ sender: UIStoryboardSegue) {
UserDefaults.standard.set(true, forKey: "firstTimer")
let mainPlayer = PlayerData()
let player1 = Player(entity: Player.entity(), insertInto: context)
player1.name = mainPlayer.name
performSegue(withIdentifier: "toMainViewController", sender: self)
saveItems()
}
func saveItems() {
do {
try context.save()
print("File Successfully saved!")
}catch {
print("Error saving Context \(error)")
}
}
// MARK: Function to Save and Load data??
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
func loadItems() {
let request = Player.fetchRequest() as NSFetchRequest<Player>
do {
player = try context.fetch(request)
print("Info loaded")
} catch {
print("Error fetching data from context \(error)")
}
}
}
MainViewController being sent the information. I only want to send one item and save it to that main view controller.
import UIKit
import Foundation
import CoreData
class MainViewController: UIViewController {
//set up model object, buttons, and labels
// let player: Player!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
// lazy var nameText = Player(context: context)
// var playerInfo = [Player]()
lazy var player = [Player]()
let playerData = PlayerData()
var name = ""
#IBOutlet weak var playerName: UILabel!
#IBOutlet weak var currentLevel: UILabel!
#IBOutlet weak var xpCounter: UILabel!
#IBOutlet weak var playerProfileImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// loadItems()
// name = playerData.name
if let nameOfPlayer = name.name {
print("This is what we see: \(nameOfPlayer)")
playerName.text = nameOfPlayer
}
appDelegate.saveContext()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// loadView()
}
#IBAction func menuButtonPressed(_ sender: Any) {
}
func loadItems() {
let request = Player.fetchRequest() as NSFetchRequest<Player>
do {
player = try context.fetch(request)
print("Info loaded")
} catch {
print("Error fetching data from context \(error)")
}
}
// MARK : Add Name to Main View
// MARK : Add Xp To Main View
// MARK : Add UI Image to profile image view
// MARK: (Optional) Create a 'Choose a task button to segue to the task tab'
// MARK: Program the Progress Bar to update on xp gained and reset on level up
// MARK: Function to Save and Load data??
}
If dataSource code needed I will add upon request.
Thanks!

Code is wrong. You should do more check

This is what I was able to achieve:
import UIKit
import CoreData
class FirstTimeViewController: UIViewController {
private var player = [Player]()
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBOutlet weak var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// This View Controller will only be used once upon the first time the app is being used.
// MARK: Make func that prepares for segue on initial opening of app
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMainViewController" {
let entity = NSEntityDescription.entity(forEntityName: "Player", in: context)
let newPlayer = NSManagedObject(entity: entity!, insertInto: context)
if let newUser = self.nameTextField.text{
newPlayer.setValue(newUser, forKey: "name")
print("This is what i got: ", newPlayer)
}
appDelegate.saveContext()
}
}
#IBAction func continueButtonPressed(_ sender: UIStoryboardSegue) {
UserDefaults.standard.set(true, forKey: "firstTimer")
performSegue(withIdentifier: "toMainViewController", sender: self)
}
And for the Main View Controller:
import UIKit
import Foundation
import CoreData
class MainViewController: UIViewController {
//set up model object, buttons, and labels
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
lazy var player = [Player]()
#IBOutlet weak var playerName: UILabel!
#IBOutlet weak var currentLevel: UILabel!
#IBOutlet weak var xpCounter: UILabel!
#IBOutlet weak var playerProfileImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
// request.predicate = NSPredicate(format: "name = %#", "noon")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "name") as! String)
self.playerName.text = data.value(forKey: "name") as? String
}
} catch {
print("Failed")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// loadView()
}
#IBAction func menuButtonPressed(_ sender: Any) {
}

Related

How can I pass dictionary from one view controller class to another?SWIFT

I am trying to make a list of users and their passwords in one view controller, save that information in a dictionary, and send that dictionary to another view controller which asks the user to input their username/password combination to authorize the log in. (the key is the username and the value is the password). Is there a way I can send the dictionary from SecondVC to the FirstVC?
First View Controller
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var Username: UITextField!
#IBOutlet weak var Verification: UILabel!
#IBOutlet weak var Password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Username.delegate = self
Password.delegate = self
}
var usersDict = [String : String]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = usersDict
}
}
#IBAction func Authorization(_ sender: Any) {
for ( key , value ) in usersDict{
let v = key.count
var start = 0
if start <= v{
if Username.text == key{
if Password.text == value{
Verification.text = "Looks Good"
}
}
else{
start += 1
}
}
else{
Verification.text = "Yikes"
}
}
}
}
Second View Controller
class AccountViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var CreateUsername: UITextField!
#IBOutlet weak var CreatePassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
CreateUsername.delegate = self
CreatePassword.delegate = self
// Do any additional setup after loading the view.
}
var usersDict = [ String : String ]()
#IBAction func MakeANewAccount(_ sender: Any) {
usersDict[CreateUsername.text!] = CreatePassword.text!
}
}
I have made there dictionary, but it will only send in the beginning and won't update after creating the original account. (dictionary it is sending is empty)
With a segue add this method inside ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = yourDicHere
}
}
Here's a general pattern for making a controller work with data from some object it creates, in this case a second controller.
Try applying it to your situation and let me know if you run into problems.
protocol Processor {
func process(_ dict: [String : String])
}
class FirstController: UIViewController, Processor {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? SecondController {
controller.delegate = self
} else {
print("Unexpected view controller \(segue.destination)")
}
}
func process(_ dict: [String : String]) {
}
}
class SecondController: UIViewController {
var delegate: Processor?
func someWork() {
if let processor = delegate {
processor.process(["Name" : "Pwd"])
} else {
print("Delegate not assigned")
}
}
}

Duplicate values after saving to CoreData

I currently have 2 views in my project: a ViewController with a TableView that shows a list of Transactions and a ViewController for creating a new Transaction.
The issue that I am having is that after creating a new Transaction, it successfully loads onto my TableView, but when I force close and reopen the app, the value is duplicated. As far as I can tell and know, the save() function is only being called once and nothing of the sort is going on in AppDelegate/SceneDelegate. See GIF for an example of what's going on.
GIF Demonstrating Issue
This is my TranasctionsTableViewController:
class TransactionsTableViewController: UIViewController {
// MARK: - Properties
var entries: [NSManagedObject] = []
var container: NSPersistentContainer!
// MARK: - IBOutlets
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// setup persistent container
container = NSPersistentContainer(name: "expenses")
// load store
container.loadPersistentStores { storeDescription, error in
if let error = error {
print("Unresolved error \(error)")
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.identifier! {
case "NewTransactionSegue":
// pass an entry to the destination segue
let destination = segue.destination as! UINavigationController
let targetController = destination.topViewController as! NewTransactionViewController
let managedContext = container.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Entry", in: managedContext)!
let entry = NSManagedObject(entity: entity, insertInto: managedContext)
targetController.entry = entry as? Entry
default:
print("Unknown segue: \(segue.identifier!)")
}
}
#IBAction func unwindToTransactionList(sender: UIStoryboardSegue) {
print("unwind segue called!")
if let sourceViewController = sender.source as? NewTransactionViewController, let entry = sourceViewController.entry {
print(entry)
self.save(entryDescription: entry.entryDescription, amount: entry.amount, date: entry.date, id: entry.id)
// reset the tableView to defaults if no data message was displayed before loading data.
if self.tableView.backgroundView != nil {
self.tableView.backgroundView = nil
self.tableView.separatorStyle = .singleLine
}
self.tableView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// load managedCOntext
let managedContext = container.viewContext
// try to fetch data from CoreData. If successful, load into entries.
do {
entries = try managedContext.fetch(Entry.fetchRequest())
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
// save a new entry to the data store
func save(entryDescription: String, amount: Double, date: Date, id: UUID) {
print("save called!!")
let managedContext = container.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Entry", in: managedContext)!
let entry = NSManagedObject(entity: entity, insertInto: managedContext)
entry.setValue(entryDescription, forKey: "entryDescription")
entry.setValue(amount, forKey: "amount")
entry.setValue(date, forKey: "date")
entry.setValue(id, forKey: "id")
do {
try managedContext.save()
entries.append(entry)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
// save the current container context
func saveContext() {
if container.viewContext.hasChanges {
do {
try container.viewContext.save()
} catch {
print("An error occurred while saving: \(error)")
}
}
}
}
This is my NewTransactionViewController:
class NewTransactionViewController: UIViewController, UITextFieldDelegate {
/*
This value is either passed by `TransactionTableViewController` in `prepare(for:sender:)`
or constructed as part of adding a new transaction.
*/
var entry: Entry?
// MARK: - IBOutlets
#IBOutlet weak var transactionDescriptionLabel: UILabel!
#IBOutlet var transactionDescriptionTextField: UITextField!
#IBOutlet var transactionAmountLabel: UILabel!
#IBOutlet var transactionAmountTextField: UITextField!
#IBOutlet weak var cancelButton: UIBarButtonItem!
#IBOutlet weak var saveButton: UIBarButtonItem!
// MARK: Constants
let TRANSACTION_DESCRIPTION_TEXT_FIELD_TAG = 0
let TRANSACTION_AMOUNT_TEXT_FIELD_TAG = 1
override func viewDidLoad() {
super.viewDidLoad()
// Handle textfield input through delegate callbacks.
transactionDescriptionTextField.delegate = self
transactionAmountTextField.delegate = self
// Adds done button to keypad
transactionAmountTextField.addDoneButtonToKeyboard(myAction: #selector(self.transactionAmountTextField.resignFirstResponder))
}
// MARK: - UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// If the First Responder is the Description Text Field, transfer First Responder to Amount Text Field
if (textField.tag == TRANSACTION_DESCRIPTION_TEXT_FIELD_TAG) {
transactionAmountTextField.becomeFirstResponder()
}
textField.resignFirstResponder()
return true
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: segue)
// Configure the destination view controller only when the save button is pressed.
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("The save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
let transactionDescription = transactionDescriptionTextField.text ?? ""
let transactionAmount = Double(transactionAmountTextField.text!) ?? 0.00
// Set the entry to be passed to TransactionTableViewController after the unwind segue.
entry?.setValue(transactionDescription, forKey: "entryDescription")
entry?.setValue(transactionAmount, forKey: "amount")
entry?.setValue(UUID(), forKey: "id")
entry?.setValue(Date(), forKey: "date")
}
// MARK: - IBActions
#IBAction func cancelButtonTapped(sender: UIBarButtonItem) {
}
#IBAction func saveButtonTapped(sender: UIBarButtonItem) {
}
}

Swift 3 - Passing data between a View Controller and after that to another 2

I'm trying to perform a segue which it doesn't work.
What i'm trying to do is send the data which i have in a textfield in my View Controller(Main), after that i want to send it to a ViewController called OperationsController and after that send it to another Views (CreateController & ListController) so i can use that same data and send it to a php file and get data to populate a table view in ListController. And for CreateController to get the email (which is in short words the data) and perform a query based on the email and insert into the database.
Anyways i tried sending the data to Operations into a label and doesn't work.
This is my code
ViewController: .
import UIKit
class ViewController: UIViewController {
var datas:[Usuario]?
struct Usuario : Codable {
let correo: String?
let contrasena: String?
}
#IBOutlet weak var txtError: UILabel!
#IBOutlet weak var txtCorreo: UITextField!
#IBOutlet weak var txtContra: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnLogear(_ sender: Any) {
let urlString = "http://localhost:8080/swiftdb/logear.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let articlesData = try JSONDecoder().decode([Usuario].self, from: data)
//Get back to the main queue
DispatchQueue.main.async {
self.datas = articlesData
let aarti = self.datas
for item in aarti! {
let correos = item.correo
let contras = item.contrasena
if(item.correo == self.txtCorreo.text && item.contrasena == self.txtContra.text){
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "OP") as! OpcionesController
self.present(nextViewController, animated:true, completion:nil)
self.performSegue(withIdentifier: "segue", sender: self)
self.txtError.text = " "
} else {
self.txtError.text = "Datos Incorrectos"
}
}
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? OpcionesController{
destination.name = txtCorreo.text
}
}
}
OperationsController: .
import UIKit
class OpcionesController: UIViewController {
var name: String?
#IBOutlet weak var displayLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let nametoDisplay = name {
displayLbl.text = name
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Before calling presentViewController add :
nextViewController.name = yourTextField.text
You could also delete the segue call. That is redundant.
Here is an example that I've used in the past :
#IBAction func doSegue(_ sender: UIButton) {
buttonTag = sender.tag
let storyboard = UIStoryboard (name: "Main", bundle: nil)
let resultVC = storyboard.instantiateViewController(withIdentifier: "ResultViewController")as! ResultViewController
// Communicate with new VC - These values are stored in the destination
// you can set any value stored in the destination VC here
resultVC.firstValue = buttonTag
resultVC.secondValue = randomOpponentValue()
self.navigationController?.pushViewController(resultVC, animated: true)
}
1.So get rid of this code, because if you are calling performSegue you don’t need that one.
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "OP") as! OpcionesController
self.present(nextViewController, animated:true, completion:nil)
2.Then in the prepareForSegue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == “YourSegueIdentifier" {
let destination: OpcionesController = segue.destination as! OpcionesController
destination.name = txtCorreo.text
}
}
3.Replace this code:
if let nametoDisplay = name {
displayLbl.text = name
}
with:
displayLbl.text = name

Add elements to search history?

I have a model - Movies.
and two controllers - first for search movie by title, second - for display result with poster, title and year.
Now i need to create some history search on my third controller
(searchHistoryController - TableView) where displayed all movies, and when i tapped on cell with movie's title show movie info.
How I can build it?
I tried create array in my model. And write resutl in it, but each time when i use search it rewrite array, not add new element.
Maybe use realm
Need some help:)
Movie.swift
import Foundation
import UIKit
import Alamofire
import AlamofireImage
protocol MovieDelegate {
func updateMovieInfo()
}
class Movie {
private let omdbUrl = "http://www.omdbapi.com/?"
var title: String?
var filmYear: String?
var poster: String?
var delegete: MovieDelegate!
var historyMovie = [Movie]()
func getMovieInfo(title: String, completion: #escaping ()->()){
let params = ["t": title]
Alamofire.request(omdbUrl, method: .get, parameters: params).validate(statusCode: 200..<300).validate(contentType: ["application/json"]).responseJSON { (response) in
switch response.result {
case .success(let JSON):
let response = JSON as! NSDictionary
let status = response["Response"] as! String
if status == "True" {
self.title = (response["Title"] as! String)
self.filmYear = (response["Year"] as! String)
self.poster = (response["Year"] as! String)
// self.delegete.updateMovieInfo()
completion()
} else {
self.title = (response["Error"] as! String)
completion()
}
case .failure(let error):
print (error)
}
}
}
}
SearchVC
import UIKit
class SearchViewController: UIViewController {
var movie = Movie()
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
#IBOutlet weak var searchTextField: UITextField!
#IBOutlet weak var searchButton: UIButton!
#IBAction func searchButtonTapped(_ sender: UIButton) {
activityIndicator.startAnimating()
DispatchQueue.main.async {
self.movie.getMovieInfo(title: self.searchTextField.text!, completion: {
self.activityIndicator.stopAnimating()
self.performSegue(withIdentifier: "movieInfo", sender: self)
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! DetailInfoViewController
secondVC.movieTitle = movie.title!
}
}
DetailVC
class DetailInfoViewController: UIViewController, MovieDelegate {
#IBAction func showHistory(_ sender: UIButton) {
performSegue(withIdentifier: "showHistory", sender: self)
}
#IBOutlet weak var posterImageView: UIImageView!
#IBOutlet weak var filmNameLabel: UILabel!
#IBOutlet weak var filmYearLabel: UILabel!
var movie = Movie()
var movieTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
self.movie.getMovieInfo(title: movieTitle ) {
self.updateMovieInfo()
}
self.movie.delegete = self
}
func updateMovieInfo() {
getPoster(link: movie.poster)
filmNameLabel.text = movie.title
filmYearLabel.text = movie.filmYear
}
func getPoster(link: String?) {
if link != nil {
guard let url = URL(string: link!) else { return }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
self.posterImageView.image = UIImage(data: data)
}
}
} } else {
self.posterImageView.image = #imageLiteral(resourceName: "Image")
}
}
}
First of all, movieHistory should not be part of your Movie class, but part of your SearchViewController class.
Second of all, unless you want to persist your data, you don't need Realm for this.
Just save the movies in SearchViewController into an array once the search button has been tapped and send it to your other view controller in the segue. Like so
#IBAction func searchButtonTapped(_ sender: UIButton) {
activityIndicator.startAnimating()
DispatchQueue.main.async {
self.movie.getMovieInfo(title: self.searchTextField.text!, completion: {
self.activityIndicator.stopAnimating()
movieHistory.append(movie)
self.performSegue(withIdentifier: "movieInfo", sender: movieHistory)
})
}
}
Also, modify prepare(for segue:...) like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! DetailInfoViewController
secondVC.movieTitle = movie.title!
secondVC.movieHistory = movieHistory
}
In detailVC override prepare(for segue:...) as well and send movieHistory to searchHistoryController the same way it is done in the previous VC.

iOS Swift - Properties not updating after preparing for segue

I am trying to pass a news ID of type string to the second VC and load the object based on it from Realm. When I debugged, I found that the prepare for segue is correctly setting the detailNewsVC.newsID to the primary key of my news item but the second VC is not receiving it. Any help on this?
Checks I have made:
Made sure that the detail VC identifier is correct
detailNewsVC.newsID in VC 1 is correctly setting the news ID .. This is to make sure that realm is correctly sending the newsID and it is working fine.
Changed the viewDidLoad in VC 2 to viewWillLoad..Just to make sure that second vc is not loaded before for any reason but no luck on that.
Restarted xcode
Replaced newsID in VC 2 with an actual news primary key and it's correctly pulling the related news. I think the culprit is that the VC2 property: newsID is not updating when prepare for segue is called.
First VC code for prep for segue:
extension HomeVC: UICollectionViewDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == SegueIdentifier.gotodetail.rawValue, let sendNewsID = sender as? String {
let navVC = segue.destination as? UINavigationController
let detailNewsVC = navVC?.viewControllers.first as! DetailNewsVC
detailNewsVC.newsID = sendNewsID
print("Detail News ID = \(detailNewsVC.newsID)")
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let newsID = newsArray[indexPath.row].newsId
performSegue(withIdentifier: SegueIdentifier.gotodetail.rawValue, sender: newsID)
}
}
Second VC Code:
class DetailNewsVC: UIViewController {
#IBOutlet private weak var scrollView: UIScrollView!
#IBOutlet private weak var newsTitle: UILabel!
#IBOutlet private weak var newsImage: UIImageView!
#IBOutlet private weak var newsDescription: UILabel!
var newsID = ""
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
print("News ID: \(newsID)")
guard let news = realm.object(ofType: News.self, forPrimaryKey: newsID as AnyObject) else {
print("Cannot load news")
return
}
print(news)
newsTitle.text = news.newsTitle
if let url = URL(string: news.urlToImage), let data = try? Data.init(contentsOf: url) {
newsImage.image = UIImage(data: data)
}
newsDescription.text = news.newsDescription
}
}
Move your prepare function out of the extension and put it in HomeVC. According to Apple's Swift Guide extensions cannot override existing functionality.
Extensions can add new functionality to a type, but they cannot override existing functionality.
Apple Developer Guide
It's hard to tell in which order UIKit calls the UIViewController methods, but it might be possible that viewDidLoad is getting called before you get the chance to set the value of newsID.
The following might be overkill, but it'll guarantee the views will be updated during viewDidLoad, or otherwise if newsID is set after the fact:
class DetailNewsVC: UIViewController {
#IBOutlet private weak var scrollView: UIScrollView!
#IBOutlet private weak var newsTitle: UILabel!
#IBOutlet private weak var newsImage: UIImageView!
#IBOutlet private weak var newsDescription: UILabel!
public var newsID = "" {
didSet {
updateUIForNews()
}
}
override func viewDidLoad() {
super.viewDidLoad()
updateUIForNews()
}
private func updateUIForNews() {
guard !newsID.isEmpty else {
return
}
let realm = try! Realm()
print("News ID: \(newsID)")
guard let news = realm.object(ofType: News.self, forPrimaryKey: newsID as AnyObject) else {
print("Cannot load news")
return
}
print(news)
newsTitle.text = news.newsTitle
if let url = URL(string: news.urlToImage), let data = try? Data.init(contentsOf: url) {
newsImage.image = UIImage(data: data)
}
newsDescription.text = news.newsDescription
}
}

Resources