Hello I have an app with 2 views.
The cybeeview and the moreview.
I use the cybeeview to do some calculations and I want to present them in the second view.
How can pass the data from cybeeviewcontroller.m to the moreviewcontroller??
Good way would be to use delegation. You can either write your own protocol, or implement - textViewDidEndEditing: inside moreviewcontroller. You will also need to set up the delegate:
cybeeviewcontroller.yourTextView.delegate = moreviewcontroller;
//those controllers are instances of their respective classes
Nonetheless it must be said that unless it is a minor and unimportant calculation you're doing, it's fair to say that you are not doing it correctly. You should be setting your model values and pass model around. You can get more information in documentation, look for Model-View-Controller design pattern.
Related
In regards to UITableView & UICollectionView & their respective protocols, UITableViewDataSource & UITableViewDelegate + UICollectionViewDelegate & UICollectionViewDataSource -- are there any pros and cons to implementing one method, but not the other or a particular set of methods and not the rest of the methods or a unique combination of specific methods?
For example, if I am to implement heightForFooter but not viewForFooter - what happens or can happen? Does this negative impact performance or the scroll of the table view?
Is there a guide which shows that which if any methods are implemented, others should be combined and dually implemented alongside them?
Re: correctness
Is there a guide which shows that which if any methods are implemented, others should be combined and dually implemented alongside them?
Not from what I've seen, though sometimes the documentation mentions it. E.g. from the docs of NSTableViewDataSource:
If you’re not using Cocoa bindings to provide data to the table view, the following methods are required:
numberOfRows(in:)
tableView(_:objectValueFor:row:)
tableView(_:setObjectValue:for:row:) (cell-based tables only)
In general, if a method is optional, but required from the context, then there will be a runtime error telling you to implment it, e.g.:
Illegal NSTableView data source (Foo). Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
Either that, or stuff will silently fail (e.g. if you don't implement NSTableViewDelegate.tableView(_:viewFor:row:), no drawing will happen, and all your cells will just be blank views.
Re: performance
There shouldn't be any real performance difference with not implementing optional methods.
Each of these methods is likely called as if you had this in Swift (most of these frameworks are still implemented in Objective C):
let result: Result? = yourTarget.someMethod?(someArg)
which is really just shorthand for:
let result: Result? = yourTarget.responds(to: #selector(YourTargetsClass.someMethod))
? target.method(someArg)
: nil
That is, it's one dynamic method lookup to check if your object responds to the message, and one dynamic message send to actually invoke the method (for the case where it does respond).
There's a teeny tiny optimization that one could squeeze out here: if your method implementation is empty, then you're better off not having it at all. That will prevent a needless message send. Though it's obviously not something to worry about until you're sure it's a hotspot in a profiler.
Of course, nothing stops a library author from writing:
if yourObject.responds(to: #selector(someMessage)) {
doSomethingVeryExpensiveForNoGoodReason()
}
... but that's unlikely.
Say you have a class
class Example:UIViewController, UIScrollViewDelegate
{
at any point in the life of the program, there may be (say) four little scroll views which have embedded themselves in an instance of Example, so those scroll views have said
aScrollView.delegate = someExample
bScrollView.delegate = someExample
and so on.
Is that information, indeed stored somehow, with someExample?
Obviously that information is stored with aScrollView - but is it also stored with someExample? Is the delegate connection two-way or strictly one-way? Can someExample examine itself and determine all the scrollviews which have been delegate'd to someExample?
So, is there a way for someExample to list all the scroll views currently connected to it in that way??
The delegate property is just a (usually weak) reference in the object that holds it. For an object to keep back pointers to its "delegations", it would need to implement its own structure and do the bookkeeping for adding and removing itself.
Alright, I know this is a vague conceptual question, but I really need help here. Thanks in advance if you decide to take the time to read this. I would never even consider writing this much except this is such a great forum with so many helpful people I thought this would be the best place to ask.
This is all related to the question here (you don't have to look at it - I explain everything below): Pass parameter when initializing table
I've been working for days on the same problem, but I'm realizing there must be something big I'm missing. I've googled and googled and I even bought (and about 50% read) two Obj-C books a few days ago, but I'm still grasping at something that seems like it should be incredibly easy. I'm obviously pretty new to OOP, but I have mediocre skills in HTML, perl, sql, python as well as some of the ancient stuff like pascal and basic. I'm a n00b, but not a moron (well, actually this experience is changing my mind a bit).
Anyway, my end goal here is to simply create an app with 8 "normal" buttons on the first view (level 1), each doing basically does the same thing - which is to show a simple table view (level 2) with data in cells that can be clicked to continue to drill down to details (level 3). Very basic and straightforward concept. The only difference between the 8 possible level 2's is the data that will be shown. I've already constructed sql queries that work just as I want for each button.
So, here's where I stand: I have a perfectly working app that does everything from level 2 down exactly as I expect - the queries work, the tables are beautiful - so that's great.
Also, I have another nav-based app that launches on "level 1" and shows me 8 buttons (I hide the nav bar on level 1). If I click any of the buttons on level 1, the level 2 view (which is a nav bar + a table) slides into view exactly like I want. The problem is the table is just blank. No matter what I do, I can't get the level 2 in the second app to show me the data, even though I can show all of that data in the first app perfectly. For the life of me, I can't figure out how to "link" level 1 with level 2.
Hopefully you can understand this gap I'm trying to bridge. Since there are 8 possibilities for level 2 (with only very slight differences in sql queries on the same sql table), I initially tried coming up with a way of "passing" an integer to the level 2 view (in the first app) and then selecting the sql query based on what was passed (see the link above for that fiasco). Once I got that working, I planned to figure out how to make the buttons do the "passing" later. However, after about 16 hours screwing with that, I just gave up and decided to make 8 different table view controllers, all with nearly identical code except the query. That way, if I could just get a SINGLE button on level 1 to simply push to just ONE of the level 2's with NO parameters I would be a horrible but successful programmer.
Unfortunately, even that hasn't worked out for me. I have tried every possible control-drag and window/view/table combination I can think of in Interface Builder, but no matter what I try, the data never loads into the table view, even though it works great in my first app. I have gone through every line of code - they are the same except something has to "call" or "launch" the level 2 part and I'm just not getting it.
So, I'm going to break with convention/expectations here and not post any code in my question. I just want to know - how can this possibly be so difficult?? I am very analytically minded and I catch on quickly, but I have to say I have never been so humbled by a technical challenge in my life.
Can anyone explain to me, at a conceptual level, what I need to be doing here or what I'm missing? Even if you give me a link to something to read I would appreciate it very much. I have watched tens of hours of tutorials on youtube, but I'm always up for more.
Of course I'm willing to share my code, but there is so much of it and I'm so new at this I really don't know where the relevant parts are. Plus, I actually want to learn how all of this works so I can help others. If there is such a thing as PM on here I'll email it to you if you're willing to take a look. Once I get it working, I will post the code here. I have to believe there are other people looking for the same kind of thing as I am. However, more importantly, I just want to know, from a high level, what is the correct way to approach my problem? If you look at my link you can see what I've been trying (which was to pass an integer to the method that populates the table), but as I said, I basically gave up on that because I wasn't getting anywhere. People are trying to help me, but I'm an idiot.
Thanks for bearing with my agonizingly long message. If you made it this far and have some suggestions for me I'm all ears. I'll be honest, though - if you tell me I should just scrap the whole thing and use core data I'll cry. I really don't think I have the time to figure out a whole different way of managing data. As I said, I'm pretty happy with the database and the query parts of my app - it's just managing the freaking views and passing data between them that is killing me!
Any help is appreciated - thank you all so much.
If I understand your question correctly, you are asking how to initialize a view controller and pass in some data to alter its behavior. The key concept here to understand is how objects are initialized in Objective-C. One of the most common questions that developers who are new to iOS have is:
How can I pass data between my views?
Yes, eight different links there. (Okay, that eighth link is a little bit off topic, but it's close enough.) There are several ways of doing this and I'll go through them briefly. I'll also describe custom initializers, which are a relevant point as well.
Let's pretend we were building a catalog application which shows a bunch of products in various categories. Imagine that our app opens to a list of products, much like the Apple Store App. Say that when the user taps on a product we want to show a product page.
You can set properties on the "next" view controller. - Simply, we can create a UIViewController subclass and set the productID property (which we made up). Let's call our new UIViewController a ProductPageViewController. Here's how this would look:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
//Create a ProductPageViewController
ProductPageViewController *ppvc = [[ProductPageViewController alloc] initWithNibName:#"ProductPageViewController" bundle:nil];
//set the property on our ProductPageViewController
[ppvc setProductID:42];
//We would usually present the PPVC here.
//After presenting, remember to release the view controller
}
In the first line, we create the product view controller. We call alloc, then init. (The functions are wrapped - that is, we call init directly on the result of the alloc method.)
Then, we set a property of our view. The view can now be configured in viewWillAppear and all is well.
You can share the data through a persistent store. - This method works a little differently. The view controllers don't communicate at all, except for the first one presenting a second one. Whenever a value in the first view changes (that you want to persist), you write it to Core Data or NSUserDefaults. Then, the new view reads the value as it needs it.
In your first view controller:
//A method to store the data
- (void)storeData:(id)pageID{
[[NSUserDefaults setObject:pageID forKey:#"pageID"];
}
- (void)showNewPPVC{
ProductPageViewController *ppvc = [[ProductPageViewController alloc] initWithNibName:#"ProductPageViewController" bundle:nil];
//Show and then release the PPVC
}
You can use custom initializers. - This is probably the most intuitive way to do it, once you understand the concept, because this is the only one where data is actually "passed". (As opposed to method 2 where no data is directly shared and method 1 where data is passed as a property.)
Notice that in the earlier examples, I used the initWithNibName:Bundle method. You might also notice that UITableViewControllers use a different initializer, initWithStyle:. Those two initializers take in some information for the new object so that it knows how to load. Let's look at the first one first:
- (id)initWithNibName:(NSString *)nibNameOrNil Bundle:(NSBundle *)bundleNameOrNil;
The first argument tells the view controller which nib file to load up. I'm going to ignore the second argument for now, since I've never seen anything passed in except nil. Moving right along to the second example:
- (id)initWithStyle:(UITableViewStyle)style;
You can pass in one of two UITableViewStyle values here. This is one way to define the style of a table view (the other way being to modify a nib file directly).
Let's extend this concept a bit to our example. I'm now going to show you how to make your own custom initializer. Let's initialize our ProductPageViewController instance:
- (id) initWithProductID:(int)productID;
That's simple enough. Now, we need to implement the method and actually do something with the product ID. We'll start with the barebones code here, required to "mimic" the functionality of the default initializer.
- (id) initWithProductID:(int)productID{
self = [super init];
return self;
}
This method will return an initialized copy of our ProductPageViewController, however, it won't load up our UI from a NIB yet, or if this were a UITableViewController, it wouldn't set the UITableViewStyle. Let's work with a NIB first and then I'll show how to work a UITableViewController. So...
- (id) initWithProductID:(int)productID{
self = [super initWithNibName:#"ProductPageViewController" Bundle:nil];
return self;
}
Now. we have an initialized ProductPageViewController, loaded from a NIB, but it doesn't do anything yet. Notice how we don't expose the NibName and Bundle arguments, but we just pass them in ourselves. If you want, you could theoretically expose those too. Now, let's take that productID and do something with it.
- (id) initWithProductID:(int)productID{
self = [super initWithNibName:#"ProductPageViewController" Bundle:nil];
if(self){
self.prodID = productID;
}
return self;
}
With our latest changes, our "PPVC" now knows about the productID. It can query the database as you want and do things with the results. You can then run different queries based on this productID.
Two More Quick Tips:
Perhaps you want to pass in several arguments. Of course you can simply add them to them method signature - (id) initWithProductID:(int)productID andCategoryID(int)categoryID, but what happens if you have five, six, or fifty six (yea, that's a lot) arguments? I'd advise passing in a collection or array of arguments.
To use custom initializers with UITableView, you pass in a UITableViewStyle instead of a NIB name. Here's what it might look like:
- (id) initWithProductID:(int)productID{
self = [super initWithStyle:UITableViewStyleGrouped];
if(self){
self.prodID = productID;
}
return self;
}
When making your subsections, I'd suggest a combination of persistent data and custom initializers. I also advise taking a peek at the viewDidLoad and viewWillAppear methods.
I have a BankAccount model class that contains data like account number, bank name, transactions, etc. The transactions are each instances of the Transaction class and contained in the transactions NSArray.
I also have a BankAccountView that displays all this in a single view. Right now I'm passing all the data as separate variables (ie. bankAccountView.accountNumber = bankAccount.accountNumber, bankAccountView.bankName = bankAccount.bankName).
The problem/dilemma arises when I need to pass the transactions to the view. I learned to keep the model and view classes separated, so I assume it's not a good thing to just pass the transactions array of BankAccount to BankAccountView, since that array contains instances of the Transaction model class.
So what I'm doing now is this: I take each Transaction instance, translate it into an NSDictionary, then set bankAccountView.transactions to an NSArray containing those dictionaries. It works and I feel like it's keeping my model and view classes separate, but it also feels overly complex, like I'm writing a lot of code for someting that could be much simpler.
Is there a better way to do this? Thanks in advance.
I think you took it way too far already and should pass the whole BankAccount object directly. No conversion, no surroundings, just pass it all along to the view. The kind of separation you did (to me) feels a bit like shooting with cannons at flies...
My arguments:
your view is capable of displaying BankAccounts only, why not pass the object it's displaying?
this makes your interface very clear, only one property is needed: #property(...) BankAccount *bankAccount;
(EDIT) passing an object encapsulates the dependencies between all your properties. They ain't independent, they form a bank account. This should be visible.
no conversion is needed, the interface does not need to be changed if you extend the model
the very very strong division between model and view was needed only if your view was independent from the data it's displaying. There is no point in anonymizing a view that can display only one very specific type of data: a bank account.
All the conversion would only make sense if you compose your view of several reusable components. You don't seem to so this is simply unnecessary work.
MVC is still preserved: Model does not know controller or view, view does not directly know controller, controller passes data from view to model and reacts to actions.
If you do not want to pass the class directly, design a protocol that encapsulates all properties the view needs from a bank account object and have the class BankAccount implement it. However, this only makes sense if you (plan to) pass different kinds of classes as data to the view. So different that these classes don't share a common superclass capable of all these properties. If there is only this simple class or inherited classes, stay with the simplest possible solution: pass the account directly.
In order to be able to trigger redraw on changes, I suggest you use Key-Value-Observation. It's a very clean way to keep the anonymity and having to write very few code. In your init method in the view do the following for each property you'd like to observe:
[self addObserver:self forKeyPath:#"bankAccount.<property>" withOptions:0 context:#"redraw"];
Then you implement observeValueForKeyPath...:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == #"redraw") {
[self setNeedsDisplay];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
I find this to be a very clean method with very little overhead, which I happen to heavily use in my own projects.
The way I look at the MVC paradigm (and remember, it's just a paradigm — it's open to interpretation) is the Model knows nothing about the view and the view knows nothing about the model. They should not directly interact; your view should not have an instance of your model, strictly speaking.
This is where the controller becomes important. I'm going to pretend your BankAccountView has some labels to display the account info, and maybe a tableview to show all account transactions (let's pretend, just for illustration). So then you would have something like this:
MyBankAccountViewController is a UIViewController subclass (the C in MVC), whose view is an instance of BankAccountView. Your view controller also has an instance of a BankAccount. At an appropriate time (such as -viewDidLoad), you'll want to populate your view (and its subviews) with the information in your BankAccount model (self.bankAccountView.accountNameLabel.text = self.myBankAccount.name or something).
MyBankAccountViewController would also act as the delegate and datasource to the tableview in your view, providing it with cells listing information about the account's transactions.
When something happens in your view that needs to change your model (for example, the user presses a "Close this account" button), the "event" would be sent from the view to the controller (either through delegation, target action, or some other mechanism you choose). The controller then decides what to do, such as [self.bankAccount closeBankAccount];.
Like I said, this is just how I interpret MVC (I view it strictly and pedantically), and it might complicate your code more than just passing the model in directly. If you're just going to be using this view in exactly one place, and never plan on reusing it, then passing in directly would likely be simpler. Just keep in mind that comes at the expense of making it more difficult to reuse in the future (which is one of the key selling-points of MVC: your models and your views should be reusable; your controller won't be).
How does look the practical usage of IBOutletCollection? Unfortunately Apple documentation mentions it briefly without giving an wider idea of usage. OK, it maintains one-to-many relation with IB, but how to access and use particular objects efficiently? With TagName? How to ensure the order of objects?
I've recently used this to easily initialize a grid of labels. I have a n by n matrix of labels on a view, and reference each one individually (via an IBOutlet) in order to display relevant data. However when the view first loads I wanted to control the default text that displayed in all the labels. Initially i wanted a dash to display, but since this is for a client I wanted it to be easy to change. The view contents have and continue to change over time, per client requests.
Instead of writing N line of code, I created an IBOutletCollection and accomplished the same results in 4 (#property, #synthesize, and for loop). YMMV but I found it very useful in this situation.
Read again this section in the Interface Builder User Guide.
IBOutletCollections are actually just NSArrays you can connect to more than one object in IB. All the objects you connected end up in this array and can be accessed from code like any other object in an array.
I used it to minimize code. I have a range of UIViews that should react on "touch up inside" events of some UIButtons (custom mode).
I gave all UIButtons a tag (lets say 1005 to 1010) and all UIViews the same tag as the UIButton they shall respond to.
Then I connected the UIViews with the collection in Interface Builder. All UIButton touch up events go to the same function in my controller. This function gets the tag of the sender object, iterates through the NSArray list (of "IBOutletCollection(UIView)") and compares the tag. Everytime it hits, the appropriate action is done.
It is a pity that NSArrays seem not to hold the order...