I'm banging my head for some time due to this issue. I precise my scenario in detail.
I have a table view where I can add data using a popover which gets displayed on clicking the '+' button in the navigation bar. I get the values from the popover but where I'm stuck is, the data received is not getting reflected in the tableview. If I move back and forth it gets displayed. Tried to reload the table with different possibilities but nothing works.
If you do want a taste of my code, you can get it here Data stored fails to display in the table view, in one to many relationship of core data?
Could anyone solve my problem, help is very much appreciated.
The idea here is to provide a way for the Add Teams popover view controller to tell the Team table view controller to reload its table view.
In the Add Team VC swift file, define a protocol:
protocol AddTeamsDelegateProtocol {
func didAddTeam()
}
In the Add Team class, add a new delegate property which of this type:
var delegate : AddTeamsDelegateProtocol? = nil
In the same class, call the delegate method when the new Team is saved:
#IBAction func submit(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Teams", inManagedObjectContext: managedObjectContext)
let team = Teams(entity: entity!, insertIntoManagedObjectContext: managedObjectContext)
team.teamName = teamNamePO.text
team.teamImage = teamImagePO.image
do{
try managedObjectContext.save()
} catch let error as NSError{
print("\(error), \(error.userInfo)")
}
self.delegate?.didAddTeam()
dismissViewControllerAnimated(true, completion: nil)
}
In the Team table view controller, implement the didAddTeam() method:
func didAddTeam() {
let request = NSFetchRequest(entityName: "Teams")
do{
teamData = try managedObjectContext.executeFetchRequest(request) as! [Teams]
} catch let error as NSError {
print("\(error), \(error.userInfo)")
}
self.tableView.reloadData()
}
Ensure that the Team table view controller conforms to the protocol
class GroupTable: UITableViewController, NSFetchedResultsControllerDelegate, AddTeamsDelegateProtocol {
Before segueing to (or presenting) the Add Teams popover (I couldn't see how this is done in your code in the other question), set the Add Teams controller's delegate:
addTeamsVC.delegate = self
Related
I'm trying to learn MVVM and it's kind of hard to me to migrate from MVC to MVVM
I'm working with Facebook Login. Here's what I'm setup my code :
Inside ViewController :
fileprivate func facebookLoginAction(){
self.viewModel.performFacebookLogin(rootVC: self)
}
Inside ViewModel :
func performFacebookLogin(rootVC: UIViewController) {
let fbLoginManager = FBSDKLoginManager()
fbLoginManager.logIn(withReadPermissions: ["email"], from: rootVC) { [weak self] (result, err) in
if let err = err {
print(err.localizedDescription)
return
}
guard let result = result else {return}
if result.isCancelled {
return
}
if (result.grantedPermissions.contains("email")) {
self?.getFacebookUserData()
}
}
}
It's works but I'm not sure that I'm doing right MVVM pattern because I'm passing ViewController to ViewModel.
Can you give me some ideas or rules of MVVM
It looks like you've done a pretty good job of implementing MVVM here! The general rule of MVVM is that if you were to strip out all of the "visuals" from your view controller, it would look identical to your view model (ie: the view model holds all of the information). Unfortunately the API here makes it difficult to avoid passing the view controller to its model directly. One possible option is to have the view model implement a view model delegate protocol...
protocol viewModelDelegate {
func getViewController() -> viewController
}
The view Controller should implement this protocol and return self. Then, you can get your view controller by calling delegate?.getViewController().
Honestly, this kind of abuses the delegate pattern, and technically still passes the view controller TYPE to the view model haha, so this might not be the best option for you!
so I created a chat view controller using the JSQMessagesViewController following this tutorial here: https://learnappmaking.com/chat-app-ios-firebase-swift-xcode/#comment-1930 my code is more or less the same, I didn't tweak anything significant in it, the tutorial is only for a single view controller so I added another view controllers for the app but every time it perform segues, I get the error SIGABRT, no matter if I segues with performSegue or with the back button in navigation bar, it keeps giving signal SIGABRT. any help would be appreciated.
this is my viewdidload:
override func viewDidLoad() {
super.viewDidLoad()
senderId = "1111"
senderDisplayName = "Bob"
title = "Steve"
inputToolbar.contentView.leftBarButtonItem = nil
collectionView.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
let query = Constants.refs.databaseChats.queryLimited(toLast: 10)
_ = query.observe(.childAdded, with: { [weak self] snapshot in
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let text = data["text"],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
self?.messages.append(message)
self?.finishReceivingMessage()
}
}
})
// Do any additional setup after loading the view.
}
SIGABRT (signal abort) is typically from a referencing error in your storyboard. Did you ever change the name of a class or make a connection from a button of one view controller to another and then delete it? If you changed the name of a class you must make the sure the name in the code of the class matches that. If you deleted a button connection between view controllers, click on the controller itself and under the connections tab you must delete it.
So i am new to app development and i am trying to set up a very simple delegation/protocol pattern. I have been searching and trying different tutorials but can't seem to find anything that works and am getting in such a muddle. Please can somebody help. I will break i down so that its really clear as to what i need -
I have two view controllers, 'DetailedVC' and 'SelectionsVC'.
DetailedVC has a variable called -
var sendingData = (choice: "", choiceValue:0.0)
and
UIbutton buttonSelectTapped
SelectionsVC has a variable called -
var recievedData = (choice: "", choiceValue:0.0)
And all i want to do is send the data from the variable 'sendingData' in DetailedVC when the button (buttonSelectTapped) is tapped to the SelectionsVC and store it in the variable 'recievedData'. I do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
Then when the user views that controller 'SelectionsVC' at whatever stage, the data will be called in the viewDidLoad when loading that controller.
Use NSUserDefault to pass data between viewcontroller if you do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
DetailedVC Code
func viewDidLoad() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("selectedData")
}
func didTapButtonSelectTapped() {
NSUserDefaults.standardUserDefaults().setDouble(sendingData , forKey: "selectedData")
}
SelectionsVC code
func viewDidLoad() {
if(NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")) {
recievedData = NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")
}
}
But as your question title describe their is no use of protocol/delegate in above code.
Passing Data on transition from viewcontroller :
DetailedVC Code
func didTapButtonSelectTapped() {
let vc = SelectionsVC()
vc.recievedData = sendingData
self.presentViewController(vc, animated: true, completion: nil)
}
I am a new programmer using Swift.
In my project I am using coredata, and a lot of view controllers.
I have this view controllers:
Viewcontroller1 has my home Viewcontroller (VC1).
Viewcontroller2 (VC2), with a list of items reloaded from my coredata (in a table view).
Viewcontroller2 (VC3), lists the attributes of the selected item in VC2.
Viewcontroller2 (VC4), makes the user edit the attributes of selected item in VC2.
SO this is my navigation: VC1 -> VC2 -> VC3 -> VC4.
The problem:
Lets say I am in VC1 and go to VC2.
I now choose an item from view controller and it takes me to VC3 (I push VC3).
I am now in the item characteristics. which are listed from coredata.
To edit them I made a button, with a segue, VC4, where I made a view where the user can change the values of the choosen item. Once the user introduces any changes in the text fields, I do a NSFetchRequest, and update the values like this:
#IBAction func saveButton(_ sender: UIBarButtonItem) {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Simulator")
do {
let results = try context.fetch(request)
if results.count > 0 {
for result in results as! [NSManagedObject] {
result.setValue(designLabel.text, forKey: "designation")
result.setValue(typeLabel.text, forKey: "type")
result.setValue(localLabel.text, forKey: "local")
do {
try context.save()
} catch {
print("Error updating")
}
}
}
} catch {
print ("Error")
}
_ = self.navigationController?.popViewController(animated: true)
}
So now, by pressing saveButton I update local, designation and type atributes, and than pop VC3.
Now in VC3 I expected to receive the update values. Instead I am receiving the old values. This is what I have in VC3:
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
map.delegate = self
let fetchRequest:NSFetchRequest<Simulator> = Simulator.fetchRequest()
do{
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
for result in searchResults as [Simulator]{
if (String(describing: result.objectID) == choosenID)
{
self.title = result.designation
localLabel.text = "Local: \(result.local!)"
typeLabel.text = "Type: \(result.type!)
print("I found it") //to check if I go inside this = and I go inside (true)
{
}
}
catch{
print("Error: \(error)")
}
}
}
Now I press the back button and I go to VC2: some thing. The cell, which have the designation on a label still don't updated it.
So I go to VC! using back button.
Now If I move forward to VC2 or VC3 everything Is updated.
What it's happening? Why isn't it updating when I pop from VC4 to VC3?
When you go back you are going back to the view as it was, when you go forward you are reloading it. What you can do though is reload the data when you go back.
say i have one view controller (VCA), which segues to another another view controller (VCB). while preparing to segue, VCA passes its managedObjectContext (i.e. following the "tell don't ask" convention). VCA also has a function saveManagedObjectContext() that performs the saving and error handling.
so, in VCA:
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
...
func saveManagedObjectContext() {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
abort()
}
}
}
...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
...
if let vcb = segue.destinationViewController as? VCB {
vcb.managedObjectContext = managedObjectContext
}
...
}
and in VCB:
var managedObjectContext: NSManagedObjectContext? = nil
say VCB adds some entity to the managedObjectContext that was passed to it from VCA
func createSomeEntity() {
let entity = NSEntityDescription.entityForName("SomeEntity", inManagedObjectContext: managedObjectContext!)
something = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedObjectContext) as? SomeEntity
}
is there any (correct) way to call the saveManagedObjectContext() function from VCA rather than having to copy the function over to VCB?
thanks
An alternative is a singleton class.
Create a new Swift file, replace the predefined code with
class CoreDataManager: NSObject {
// MARK: - Shared Instance
class var sharedManager : CoreDataManager {
struct Singleton {
static let instance = CoreDataManager()
}
return Singleton.instance
}
// MARK: - Core Data stack
lazy var ...
}
and then replace lazy var ... with the entire Core Data stack from AppDelegate.
Now you can access Core Data from everywhere using
let managedObjectContext = CoreDataManager.sharedManager.managedObjectContext
or to call the save action
CoreDataManager.sharedManager.saveAction(self)
Good point about "tell don't ask". A lot of Apple sample code evangelizes this concept. However, in recent years, Apple has also provided sample code with a Core Data stack class that handles the object graph, practically abandoning the "tell don't ask" pattern.
Also, in many popular and acclaimed open source projects, this pattern is used. In most cases you reduce the code and still have a robust solution. For example, in more complex projects with nested background contexts it is often the only feasible setup.
Thus, I would recommend to create a CoreDataManager class that handles the core data stack, or for less complex apps (single, main thread context) use the app delegate.
Note that in Swift, you can really make this very concise with global variables (which you should use sparingly!). E.g., on top of the AppDelegate.swift you could write
let SharedAppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
(I like to capitalize my global singletons). You can use this as follows
do { try SharedAppDelegate.context.save() } catch {}