This may seem like a weird question but thought I would ask here before I spend a couple hours trying to implement it.
In my app, I am saving view properties (number of sub views etc) to an sqlite3 database.
While reading the data back, I though 'Would it not be so much easier to just store the view in the database?'
So, using something like a blob type do you think it would be possible?
Something like:
Bind blob to insert
view = sqlite3_column_blob(statement, 0); to retrieve
Does this sound like it could work? My biggest concern is knowing the size of the view on insert and select. Is something like this possible or is it not documented because it is too ridiculous?
Thanks!
and then getting it out
UIView conforms to NSCoding, which means you can serialize and deserialize it. This is exactly how nib files work. First, see the Archives and Serializations Programming Guide.
One easy way to serialize a UIView is like this:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:view]
You can then write out data in whatever way is convenient.
To deserialize the view, use this:
UIView *view = [NSKeyedUnarchiver unarchiveObjectWithData:data];
This will make a new copy of the entire view hierarchy.
Note that UIView only serializes its own properties. If you subclass UIView and want to serialize additional properties, you will need to override encodeWithCoder: and initWithCoder: to add your properties. This is detailed in Encoding and Decoding Objects.
Related
I'm having trouble understanding the idea of objects. From what I've read, they're instances of a class. When learning swift, they're quite easy to understand. Simply create a class and create an instance of it, and from there, you can modify it's properties and call its methods:
class ExampleClass {
let ExampleProperty = "rabbit"
}
let exampleInstance = ExampleClass()
But I don't see how that translates when using iOS, since I haven't seen any objects being created explicitly yet:
var example = Wss()
So my questions are:
Are things like buttons, labels, and sliders objects?
-If so, where's the "code" behind them? Why do buttons, labels, etc. display even before they're connected through outlets and actions to the View Controller? Is there a hidden "var thisButton = ThisViewController()" embedded into each of those sliders and buttons?
If my assumptions are wrong, can someone explain to me how objects work?
"Is there a hidden "var thisButton = ThisViewController()" embedded into each of those sliders and buttons?"
No, and this is exactly where interface builder excels. Much of Xcode's modern Interface Builder comes from NeXTSTEP. When you drag out a new UI component like NSButton and place it on your story board, Xcode is instantiating a new object of the NSButton class for you. When you save your file, Xcode serializes all the objects of your story board into a .nib file. At the time when this was invented, it was quite revolutionary, all made possible because of the dynamism of Objective C. It made GUI programming much simpler and dynamic. Every object in your story board is aware of its class. For example, when you instantiate a new NSButton, you can open the inspector and see for yourself that its class is NSButton. When you add custom views to your application, they keep track of their class in the same way. Whenever a nib file is loaded, these views are instantiated from their classes. You might have noticed that you never override the initializer of your views. Instead, you override methods like awakeFromNib. This is because there's a lot of behind the scenes work being done for you, from the time the object is first instantiated, to the time. During this time IBOutlets and IBActions are bound for you.
Competitors tried to make similar interface building applications, but they ultimately resorted to doing code generation behind the scenes. In these systems, when you saved your interface file, the program would generate a source file that contains code that instructs how to instantiate these objects anew whenever the interface is loaded. However, it proved significantly more complex a task then just serializing the objects, so these systems were error prone, and significantly harder to debug (because you'd be trying to debug machine generated source files).
Answering your questions:
Yes. Your objects are just being created from a NIB, or Storyboard. So the NIB, or Storyboard, will create those visual (UI) elements for you, which you can then be accessed via the IBOutlets
Your assumptions, are not completely wrong as in, there is in fact something allocating those objects for you. The NIB, or Storyboard, just describe a way for those objects to be created. Also some other customisations, like frames, colors, etc.
More about how this ties up can be found here.
Building on Alexander's answer:
UIView objects have a method init(frame:) that lets you create a new UIView object with a specified frame.
Other UIView subclasses might have init methods that take additional parameters.
UIView objects also support an init method init(coder:) that knows how to create an object from a stream of stored data. This is known as "deserializing" the object, or converting it from a byte-stream back into a running object.
When you build an object in a Storyboard or XIB file in Interface Builder, the system serializes the object into a byte stream and saves it into your Storyboard/XIB.
Then when you invoke the storyboard scene/XIB, the system reads the data stream and uses it to recreate (deserialize) the objects that are described in the storyboard/XIB.
The effect is essentially the same as if you wrote a bunch of code that created and configured all your views, but instead of writing all that code you are able to build your interface in Interface Builder, which is faster and easier to create, and MUCH faster and easier to update and maintain than a bunch of custom code.
But I don't see how that translates when using iOS, since I haven't seen any objects being created explicitly yet
There's no difference between the objects in iOS and what you understand objects to be. Objects are instances of a class. What you need to understand is that your own code is not the only place where objects can be created, and your own code will often interact with objects created outside your code. Here's a simple example:
let defaults = NSUserDefaults.standardUserDefaults()
Here defaults gets a reference to a user defaults object that the system provides. You never need to instantiate NSUserDefaults yourself.
Are things like buttons, labels, and sliders objects?
Yes, those are instances of UIButton, UILabel, and UISlider, respectively.
If so, where's the "code" behind them?
It's in the UIKit framework. You don't get to see the source code for those classes, but you can still use them by linking the framework into your app.
Why do buttons, labels, etc. display even before they're connected through outlets and actions to the View Controller?
You're talking about storyboards here. When you set up a view in Xcode's storyboard editor, the data that's stored in the storyboard file is essentially an archive containing serialized objects. When a view controller is instantiated from a storyboard, the objects in the storyboard are recreated from that data and then connected to the view controller's outlets. You can start this process yourself by instantiating a new view controller like this:
let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "MyViewController")
You don't usually need to do that, though, because the segues in your storyboard provide for transitioning between scenes, including creating the view controller that's the destination of a given segue.
I'm rather new to objective C and at the moment I'm trying to create one small project.
The task I want to accomplish is the following:
I've got the UIViewController for the screen of the game I'm creating. It has an UIImageView and a UITextView on it. What it does so far is that the latter one is moving towards the former one. And when their frames intersect (CGRectIntersectsRect) some actions happen.
What I want to achieve next is to create a specific class for UITextviews, so that there will be many of them created on the screen of UIViewController (I think array should be used here). And next I want every of them to be checking themselves, if they have an intersection with the UIImageView - than (again) something happens.
I've tried several ways like creating a mutable array, but every time I've some errors connected with variables of the original ViewController used inside of the new class (Hit).
The code I use for the one existing UITextView, that is created inside of UIViewController, is the following:
-(void)Moving{
HitR.center = CGPointMake(HitR.center.x+HitRX, HitR.center.y+HitRY);
if (CGRectIntersectsRect(HitR.frame, Finish.frame)) {
/*some actions here*/
}
}
etc
Can you help me to create these array of UItextFields, using their own class, tell them what to do with the help of properties like UIimageview.frame from the ViewController and then to place them on the screen.
P.S. I've read numerous articles about how to transfer variables from one class to another, but still failed to accomplish my aim.
I have 4 ivars:
UIView *view1;
UIView *view2;
UIView *view3;
UIView *view4;
I would like to be able to alloc and init them in a dynamic way, instead of doing:
view1 = [[MyView1 alloc] initWithFrame:....
view3 = [[MyView2 alloc] initWithFrame:....
view4 = [[MyView3 alloc] initWithFrame:....
view4 = [[MyView4 alloc] initWithFrame:....
So, I tried to use a array and store the names of these ivars in it:
[array addObject:#"view1"];
[array addObject:#"view2"];
[array addObject:#"view3"];
[array addObject:#"view4"];
And so that within a loop I would do:
[self valueForKey:[array objectAtIndex:x]] = [[[[self valueForKey:[array objectAtIndex:x]] class] alloc] initWithFrame:(CGRect){.....
The above generates the error:
Expression is not assignable.
Hope someone can tell me why the above cannot be done.
My question is:
I have a feeling that this is not a clever way of doing things.
Right now I have only 4 views, but who knows if in the future I might have more.
So, my idea is that instead of hard-coding things, I would like to find a more dynamic way of accomplishing this.
My idea is that at compile-time, all this views are just of UIViews.
And only until run-time would I resolve these views to the individual class types
(i.e. MyView1, MyView2 etc etc) and alloc and init them and assign them
accordingly to the ivars (i.e. view1, view2, view3, etc) within my class.
The reason why I use an array is because, if in the future I added another view called view5 of class type MyView5, I could loop the alloc and init process using [array count]. If this way of doing it is still not optimal, please correct me.
To sum up, I would like to set up my controller in a way that it only knows during compile-time that these objects are just of class type UIView. Only until run-time would I resolve them individually to MyView1, MyView2(subclass of UIView) etc and assign them to the ivars within my controller (again, they are named view1, view2 etc).
And if I added another view in the future, I wouldn't have to look all over the place within this controller and hard-code: view5 = [[MyView5 alloc] init....
Can someone show me how to accomplish this optimally (future-wise) and dynamically?
EDIT:
It just occurred to me: it would be even better if I could create these ivars only during runtime, so that in the future everything could be created dynamically.
If I understand what you're asking, let me provide a different approach which you might like:
// Set up a mutable array of objects
NSMutableArray *views = [[NSMutableArray alloc] init];
// Set up an array of strings representing the classes - you can add more
// later, or use -stringWithFormat: to make the class names variable
NSArray *classes = #[#"MyView1", #"MyView2", #"MyView3", #"MyView4"];
// Now loop through it and instantiate one of each kind
for (NSString *className in classes)
[views addObject:[[NSClassFromString(className) alloc] initWithFrame:CGRectZero]];
Remember to be careful with NSClassFromString, as you might accidentally send the -initWithFrame: message to a type that doesn't implement that.
Hope this helps!
Edit: I see that I have given you an overly implementation-based answer, while you seem to be looking for the program design aspect.
When you're designing your controller class, it's good that you're considering how you will use the class in the future. That said, you need to have a specific idea of how abstract you want the class to be. In other words, don't go trying to make the controller class completely decoupled, because at some point your class will be a bulk of useless management code.
So how do you go about writing a class that is both decoupled and functional at the same time? I suggest you look for examples in Apple's classes. Here are a few:
UIViewController, probably the most important and versatile class on iOS. They designed it to be easily subclassable, yet there are also many premade subclasses like the navigation controller and table view controller varieties.
UIDocument, a template for all document model objects you will ever need. The system implementation handles all the nitty-gritty of iCloud sync, file management, etc., while not knowing anything about the document contents itself. The user subclass, however, provides the necessary information in an NSData object.
UIGestureRecognizer, the foundation of the touch-based UI. Most people only use the system-provided tap/swipe/pinch subclasses, but the abstract superclass itself (whether subclassed or not) detects any gesture you want and sends the necessary messages. Again, the gesture recognizer doesn't know what views you attach it to, yet it still performs its job.
Do you see what I'm getting at here? Apple's classes illustrate that there are ways to provide the necessary functionality while staying abstract, but without going into runtime acrobatics. As one of the commenters suggested, all you really need is an array of view objects. Instead of having your controller class instantiate the views, maybe you should have your client objects do that. It's all about finding a balance between abstraction and functionality.
The problem is, that you're thinking about this, as if your calls to the array elements would replace your code before compiling (like macros would do). It just doesn't work that way. For example:
[self valueForKey:[array objectAtIndex:x]] = ...
The compiler sees [self valueForKey:[array objectAtIndex:x]] as a value and "thinks" hey you can't assing sth to a value.
Try the following (i've splitted it into multiple statements for better readability and also to make the code more self-explanatory):
string className = [array objectAtIndex:x];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:className];
Keep in mind that with this approach the property names (and btw. they also need to be properties not ivars for KVC to work) must match your class names, i.e. if your class is named MyView1 your property must also be called MyView1. You might not want this (in fact according to you description in the text, you don't). So to make it work you could create a dictionary mapping your property names to your class names and enumerate over it's keys:
NSMutableDictionary classNameMapping = [NSMutableDictionary new];
[classNameMapping setObject:#"MyView1" forKey:#"view1"];
[classNameMapping setObject:#"MyView2" forKey:#"view2"];
//...
foreach (string propertyName in [classNameMapping allKeys])
{
string className = [classNameMapping objectForKey:propertyName];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:propertyName];
}
I do a lot of animations with UIKit and I frequently store any animated view's initial frames in viewDidLoad to always have a reference to the frame as it appears in a xib.
This is kind of smelly and seems like the kind of thing that would be automated, but I can't seem to find any info on this. Is there a property on UIView that stores initial xib frame sizes? Or maybe a UIKit utility method that scans the xml of a xib for it's attribute values by name?
So while I could not find any way to access an interface builder file's initial attributes (unless you count writing an IB XML parser a way), I found a brilliant alternative from Do I need to set heightForRowAtIndexPath if I am using a custom UITableViewCell?. Instead of accessing some constant values from an object itself (I was hoping for a self.attribute.nib.value), you can make two outlets per IB object- one for manipulating, and one for a prototype- which will hold all the original values of the xib.
To makes things simple, just create an(other) outlet for your nib and override its getter.
For example, if we have a LargeCell.xib, we would create an outlet for largeCell and another for largeCellPrototype.
Then we would make a lazy accessor for this prototype to ensure the prototype is not nil and only loaded once.
- (LargeCell*)largeCellPrototype
{
if (!_largeCellPrototype)
{
[NSBundle mainBundle] loadNibNamed:NSStringFromClass([LargeCell class]) // Presuming you don't name files with reckless abandon
owner:self
options:nil];
}
}
now getting the initial values is as simple as
CGFloat initialViewHeight = self.largeCellPrototype.frame.size.height;
I've never used NSCoding before and I'm very confused about how it should be implemented.
My current iPad app has a UIImageView (called "background") which is a property of my main view controller. "background" has a UIImage "image" property (obviously) and various subviews which are added by the user. The added subviews are my own custom subclasses of UIImageView.
I need to be able to save the state of the "background" UIImageView so it can be restored with the same image and all the subviews in place as it was when archived.
I understand UIImageView conforms to the NSCoding protocol, but I'm not sure where to implement encodeWithCoder and initWithCoder. Do I call these from my main view controller? Do I need to create a category for UIImageView which allows me to override these methods?
Do I need to write code for archiving every property of my "background" UIImageView and its subviews? I have read elsewhere on SO that UIImage does not conform to NSCoding so needs to be subclassed or have a category added in order to be able to archive UIImageView.
I thought there would be a simple way to save to disk an object including all its properties, subviews etc. It seems there's a lot that needs to be done in order for me to save this "background" UIImageView and restore it later. I'm struggling to visualise everything I need to do. Any pointers much appreciated!
Serialization (aka archiving and unarchiving) is actually pretty complicated, but the degree to which Cocoa makes it easy is a pretty impressive feat.
Once you've set things up so that the UIImageView and all of its properties that you want to keep conform to NSCoding, then all you have to do to save the object is:
NSData *dataToSave = [NSKeyedArchiver archivedDataWithRootObject:yourImageView];
And then store that NSData somewhere. Then, to unarchive the object,
UIImageView *restoredImageView = [NSKeyedUnarchiver unarchiveObjectWithData:dataToRestore];
after recovering the NSData from somewhere.
As for making everything conform to NSCoding, UIImageView conforms to NSCoding, as does UIView, so between your UIImageView, its subviews, and their properties, everything probably conforms to NSCoding except for the actual UIImage. For that, if you google you can find lots of categories people have made to make it conform to NSCoding, so just include one of them in your project and you should be fine.