Cloning a uiview defined in interfacebuilder? - ios

I have a UIView created in interaface builder that is a subview of a scrollview. the UIView contains a button and a label. I would like to use this view as a cookie cutter so I can generate various instances of this view that are aligned next to each other in the scrollview
I can do this programatically but that means I have to progamatically define the view size and subviews programatically, what I would prefer to do is define one instance of the view in interface builder so I can lay it out and then programatically create copies of this view. The goal is to use interfacebuilder as much as possible for defining layouts to reduce the code that I need to write.

I created a UIView category to handle this.
#interface UIView (JLTDeepClone)
- (id)deepClone;
#end
#implementation UIView (JLTDeepClone)
- (id)deepClone
{
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self forKey:#"view"];
[archiver finishEncoding];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
UIView *result = [unarchiver decodeObjectForKey:#"view"];
[unarchiver finishDecoding];
return result;
}
#end

Have a look at the Entity Framework it sounds like what you are looking for and should help reduce the lines of code
http://msdn.microsoft.com/en-US/data/ef

Related

_tableView reloadData not working when i called method from another class

This table view is working fine when tableview is firstly loaded
but when I tried to reload data using [_tableView reloadData], suddenly list won't reload at all.
Here's code:
-(void)loadListLoop{
AppDelegate* delegate = [[UIApplication sharedApplication] delegate];
shuffleLbl.hidden=false;
[self.view bringSubviewToFront:shuffleLbl];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSString*playlistID=delegate.gPlaylistID;
NSString*token=[ud stringForKey:#"youtube_token"];
NSString *origin = [NSString stringWithFormat: #"https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=%#&key=AIzaSyBeFK_llQHRl7TyXoQxGkLDmIfKGzOPezM&access_token=%#",playlistID,token];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:origin]];
NSData *json = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSArray *array = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:nil];
_titleList=[array valueForKeyPath:#"items.snippet.title"];
_thumbnailList=[array valueForKeyPath:#"items.snippet.thumbnails.default.url"];
_idList=[array valueForKeyPath:#"items.snippet.resourceId.videoId"];
NSLog(#"%#",_titleList);
[[NSRunLoop currentRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
dispatch_sync(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
}
The method loadListLoop is called from another class using:
PlaylistDetailViewController *playlistDetail = [[PlaylistDetailViewController alloc] init];
[playlistDetail loadList];
Looks like loadListLoop is successfully called and everything before [_tableView reloadData]; is also successfully loaded.
I put NSLog inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
to see is app is at least trying to reload data but it seems its not working at all.
EDIT:
first,view controller that contains "-(void)loadListLoop" is container view.so target view controller should be on screen
EDIT2:
i defined outlet at .h file below
#import <UIKit/UIKit.h>
#interface PlaylistDetailViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>{
//IBOutlet UITableView *tableView;
IBOutlet UIButton *shuffleLbl;
}
-(void)exitLoopVoid;
-(void)loadListLoop;
#property (nonatomic,strong) IBOutlet UITableView *tableView;
#property (nonatomic,retain) NSMutableArray *titleList;
#property (nonatomic,retain) NSMutableArray *authorList;
#property (nonatomic,retain) NSMutableArray *thumbnailList;
#property (nonatomic,retain) NSMutableArray *idList;
-(IBAction)shuffle;
#end
IMPORTANT
EDIT3:
looks like some thing very weird is happening to NSMutableArray.
overwrite NSMutableArray at loadListLoop method(works fine and checked content with NSLog)
reload table(seems this is working fine too)
content inside NSMutableArray will rollback to old content
anyone has idea about this issue?
EDIT4:
1.overwrite NSMutableArray at loadListLoop method(success)
2.reload table and NSMutableArray will be null only at this method
3.rollback to data that i overwrites data at loadListLoop
I see a couple of problems.
First, the code you posted is a method loadListLoop. But the code that you posted is calling a different method loadList. The two are not connected.
Second, you say:
the method "loadListLoop" is called from another class using
PlaylistDetailViewController *playlistDetail =
[[PlaylistDetailViewController alloc] init];
[playlistDetail loadList];
That code is very wrong. It is creating a brand new instance of PlaylistDetailViewController that is not on-screen, and invoking the loadList method on that newly created view controller.
The view controller hasn't had a chance to display itself yet, so it's view properties will be nil. Plus, the view controller probably doesn't have any data in the model structure it uses to populate it's table view, so it won't have anything to display.
Further, if your view controller's view structure is defined in a storyboard, you can't use alloc/init to create new view controller instances.
At the point where you're trying to call loadList/loadListLoop, is the target view controller on screen? You need to explain your calling sequence.
use NSNotificationCenter for Update the table from one class to another class
1.do this in from where you have to call.
[[NSNotificationCenter defaultCenter] postNotificationName: #"UpdateTable" object:nil userInfo:userInfo];
2.And use this one for table view class. Write this in ViewDidLoad method.
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(refresh_method)
name:#"UpdateTable"
object:nil];
3.
-(void)refresh_method
{
//reload table here.
}

Why is dealloc called immediately after the instantiation?

I have a small problem with ARC and dealloc of the BaseViewController class being called after the instantiation inside the loop and I don't know why. What I'm trying to do is basically store all the base view controllers on an array.
#interface CategoriesContainerViewController ()
#property (nonatomic, strong) IBOutlet UIScrollView* scrollView;
#property (nonatomic, strong) NSMutableArray* categoriesViews;
#end
- (void)viewDidLoad {
[super viewDidLoad];
// Get the categories from a plist
NSString* path = [[NSBundle mainBundle] pathForResource:#"categories" ofType:#"plist"];
NSDictionary* dict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSMutableArray* categories = [dict objectForKey:#"Categories"];
NSLog(#"%i", [categories count]);
// Setup the scrollview
_scrollView.delegate = self;
_scrollView.directionalLockEnabled = YES;
_scrollView.alwaysBounceVertical = YES;
_scrollView.scrollEnabled = YES;
CGRect screenRect = [[UIScreen mainScreen] bounds];
// Loop through the categories and create a BaseViewController for each one and
// store it in an array
for (int i = 0; i < [categories count]; i++) {
BaseViewController* categoryView = [[BaseViewController alloc]
initWithCategory:[categories objectAtIndex:i]];
CGRect frame = categoryView.view.frame;
frame.origin.y = screenRect.size.height * i;
categoryView.view.frame = frame;
[_scrollView addSubview:categoryView.view];
[_categoriesViews addObject:categoryView];
}
}
You are committing a common beginner mistake by keeping a reference to a view controller's view, but not the view controller itself.
You create a BaseViewController object in a local variable categoryView. That's a strong reference, so the object is kept around. Then the loop repeats, and you create a new BaseViewController, replacing the old value in categoryView. When you do that, there are no longer any strong references to the previous BaseViewController that was in categoryView, so it gets deallocated.
If you want the BaseViewController to stick around, you need to keep a strong reference to it somewhere.
In addition to that, you are breaking another rule of iOS development. You should never put one view controller's view(s) inside another view controller's unless you use the parent/child view controller support that was added in iOS 5 and extended in iOS 6. The docs say do NOT do that.
Mixing views from multiple view controllers on the screen will cause you no end of problems. There is tons of housekeeping you have to do in order to make it work, and not all of that housekeeping is documented. Its possible, but it will take you many weeks to iron out the bugs, if you ever able to. Plus, since you are doing something that Apple expressly says not to do, the burden is on you to make it work correctly, and there is a substantial risk that a new iOS release will break your app.
Initialize BaseViewController above for loop and then store the array value inside the object of BaseViewController. Because every time it is allocating and initializing. So setting the previous object to nil. Hence the issue causes to be deallocated.

UIImageView not displaying images

For some reason I cannot get an image album into my UIImageView. It is added in storyboard and declared:
#property (strong, nonatomic) IBOutlet UIImageView *displayAlbum;
#synthesize displayAlbum, titleLbl, artist;
In my code:
displayAlbum = [[UIImageView alloc] init];
MPMediaPropertyPredicate *predicate = [MPMediaPropertyPredicate predicateWithValue:#"1079287588763212246" forProperty:MPMediaEntityPropertyPersistentID];
MPMediaQuery *query = [[MPMediaQuery alloc] init];
[query addFilterPredicate: predicate];
NSArray *queryResults = [query items];
for (MPMediaItem *song in queryResults) {
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
[displayAlbum setImage: [artwork imageWithSize:CGSizeMake(displayAlbum.frame.size.width, displayAlbum.frame.size.height)]];
}
I am able to get other song information so its definitely getting the right song, but the ImageView always shows up blank.
On a side note, if anybody could help me clean up the above bit of code, especially get rid of the for loop (it should always only return one result anyways) that would be great. Thanks
It's because you're creating a new image view with alloc init, instead of using the one you created in IB. You should just delete that alloc init line.
Why are you allocating displayAlbum, when it is IBOutlet? If it is already made in NIB, then by allocating new one, it should not work, as you will have completelly new UIImageView which is not part of your view controller. If it is not made in NIB, then you should add it to your view controller by adding subview of some of your views that are part of your view controller and set some frame for it.
At first, try to comment this line:
displayAlbum = [[UIImageView alloc] init];

Adding and removing views dynamically

I want to achieve effect similar to this: jsfiddle net/7pF22/ I need to be able to show more buttons/labels on demand. I thought about putting additional stuff to another view, loading xib and displaying it when receiving tap event. Unfortunately the rest of the main view is not scrolling down. So I believe that there are better ways to do this (maybe using tableview? but in my case cells doesn't have much in common)
Put all views/rows in array , get index of view that you are scaling and run a loop moving all others inside UIView animation .
Firstly, you create a NSMutableArray to store your views.
Secondly, you could use #import and NSClassFromString to get your view's class.
And then, add views.
#import <objc/runtime.h>
NSMutableArray *arr = [[NSMutableArray alloc] initWithObjects:#"OneViewClassName", #"TwoViewClassName"];
for (NSString *className in arr) {
UIView *view = [[NSClassFromString(className) alloc] init];
[self.view addSubview:view];
[view release];
}

iOS loadNibNamed confusion, what is best practice?

I'm familiar with most of the process of creating an XIB for my own UIView subclass, but not everything is working properly for me - it's mostly to do with the IBOutlets linking up. I can get them to work in what seems like a roundabout way.
My setup is this:
I have MyClass.h and MyClass.m. They have IBOutlets for a UIView (called view) and a UILabel (called myLabel). I added the 'view' property because some examples online seemed to suggest that you need this, and it actually solved an issue where I was getting a crash because it couldn't find the view property, I guess not even in the UIView parent class.
I have an XIB file called MyClass.xib, and its File's Owner custom class is MyClass, which prefilled correctly after my .h and .m for that class existed.
My init method is where I'm having issues.
I tried to use the NSBundle mainBundle's 'loadNibNamed' method and set the owner to 'self', hoping that I'd be creating an instance of the view and it'd automatically get its outlets matched to the ones in my class (I know how to do this and I'm careful with it). I then thought I'd want to make 'self' equal to the subview at index 0 in that nib, rather than doing
self = [super init];
or anything like that.
I sense that I'm doing things wrong here, but examples online have had similar things going on in the init method, but they assign that subview 0 to the view property and add it as a child - but is that not then a total of two MyClass instances? One essentially unlinked to IBOutlets, containing the child MyClass instantiated via loadNibNamed? Or at best, is it not a MyClass instance with an extra intermediary UIView containing all the IBOutlets I originally wanted as direct children of MyClass? That poses a slight annoyance when it comes to doing things like instanceOfMyClass.frame.size.width, as it returns 0, when the child UIView that's been introduced returns the real frame size I was looking for.
Is the thing I'm doing wrong that I'm messing with loadNibNamed inside an init method? Should I be doing something more like this?
MyClass *instance = [[MyClass alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:instance options:nil];
Or like this?
MyClass *instance = [[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:nil options:nil] objectAtIndex:0];
Thanks in advance for any assitance.
The second option is the correct one. The most defensive code you could do is like this:
+ (id)loadNibNamed:(NSString *)nibName ofClass:(Class)objClass {
if (nibName && objClass) {
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:nibName
owner:nil
options:nil];
for (id currentObject in objects ){
if ([currentObject isKindOfClass:objClass])
return currentObject;
}
}
return nil;
}
And call like this:
MyClass *myClassInstance = [Utility loadNibNamed:#"the_nib_name"
ofClass:[MyClass class]];
// In my case, the code is in a Utility class, you should
// put it wherever it fits best
I'm assuming your MyClass is a subclass of UIView? If that's the case, then you need to make sure that the UIView of your .xib is actually of MyClass class. That is defined on the third Tab on the right-part in the interface builder, after you select the view
All you need to do is create the subview via loadNibNamed, set the frame, and add it to the subview. For example, I'm adding three subviews using my MyView class, which is a UIView subclass whose interface is defined in a NIB, MyView.xib:
So, I define initWithFrame for my UIView subclass:
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"%s", __FUNCTION__);
self = [super initWithFrame:frame];
if (self)
{
NSArray *nibContents =
[[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
[self addSubview:nibContents[0]];
}
return self;
}
So, for example, in my UIViewController, I can load a couple of these subclassed UIView objects like so:
for (NSInteger i = 0; i < 3; i++)
{
CGRect frame = CGRectMake(0.0, i * 100.0 + 75.0, 320.0, 100.0);
MyView *myView = [[MyView alloc] initWithFrame:frame];
[self.view addSubview:myView];
// if you want, do something with it:
// Here I'm initializing a text field and label
myView.textField.text = [NSString stringWithFormat:#"MyView textfield #%d",
i + 1];
myView.label.text = [NSString stringWithFormat:#"MyView label #%d",
i + 1];
}
I originally advised the use controllers, and I'll keep that answer below for historical reference.
Original answer:
I don't see any references to view controllers here. Usually you'd have a subclass of UIViewController, which you would then instantiate with
MyClassViewController *controller =
[[MyClassViewController alloc] initWithNibName:#"MyClass"
bundle:nil];
// then you can do stuff like
//
// [self presentViewController:controller animated:YES completion:nil];
The NIB file, MyClass.xib, could specify that the base class for the UIView, if you want, where you have all of the view related code (e.g. assuming that MyClass was a subclass of UIView).
Here's one method that I use:
Create a subclass for UIView, this will be called MyClass
Create a view xib file. Open in interface builder,
click File's Owner and in the Identity Inspector, change the class
to that of your parent view controller, e.g.
ParentViewController.
Click the view already in the list of objects and change it's
class in Identity Inspector to MyClass.
Any outlets/actions that you declare in MyClass will be
connected by click-dragging from View (not File's Owner). If you want to connect them to variables from ParentViewController then click-drag from File's Owner.
Now in your ParentViewController you need to declare an instance
variable for MyClass.
ParentViewController.h add the following:
#class MyClass
#interface ParentViewController : UIViewController {
MyClass *myClass;
}
#property (strong, nonatomic) MyClass *myClass;
Synthesize this in your implementation and add the following in your
viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:self options:nil];
self.myClass.frame = CGRectMake(X,Y,W,H); //put your values in.
[self.view addSubview:self.myClass];
}

Resources