I've been trying to subclass UICollectionReusableView in a non-storyboard iPad project. I've built a view in IB and hooked it up to my custom class, registered the class for reuse in the viewController where my collection view lives, and am calling it correctly in
UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
However, nothing shows up in my header areas in the UICollectionView. I think I need too init the view with coder, but am unsure how to do that correctly. I followed a couple of examples I found, but the header view still does not appear in my collection view.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[[NSBundle mainBundle] loadNibNamed:#"CVHeaderView" owner:self options:nil];
[self addSubview:self.categoryNameLabel];
}
return self;
}
Can anyone point me in the right direction?
If you use a storyboard and select the header/footer checkmark initWithCoder: will be called.
If you do not use the storyboard (or do not click header/footer) but hook it up manually you have to register your custom classes and initWithFrame: will be called.
[self.collectionView registerClass:[GameCardCell class] forCellWithReuseIdentifier:#"GameCardCell"];
[self.collectionView registerClass:[PlayerHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"PlayerHeaderView"];
[self.collectionView registerClass:[PlayerFooterView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:#"PlayerFooterView"];
Note: Both will be called only once. If the view comes out of the cache prepareForReuse will be called.
In my case, initWithFrame: is called automatically when dequeueing it for the first time. Try to implement this method and see if it works.
Related
I'm new to iOS programming, I read a lot of tutorials and forums but I still can't figure the best way to manage a project.
What I want is the iPad screen to display a CollectionView and a TableView side by side.
Actions in the CollectionView should change TableView content. SplitViewController won't do the job because of the fixed size of the split.
For now I'm using Storyboard, I created a ViewController and added two ContainerViews in it. Each container is linked by an XCode generated segue to a view controller (LeftViewController and RightViewController).
I'm trying to figure the smartest way to manage actions on the LeftViewController and send changes to the RightViewController.
I would like to use Storyboard that seems more elegant, but I'm not sure how to implement this.
Assuming you know the way to establish delegate methods (#protocol, see here for links), the key elements will be grabbing the two viewControllers embedded in containers as they are being loaded, setting the primary viewController as delegate, and sending the messages when something changes. For starters, if communication needs to flow both ways between controllers, set up an instance variable for each VC.
Given VCPrime, CollectionVC, and TableVC:
First, in storyboards, name each of your segues (from the containerViews to the VCs). In VCPrime, implement prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"collection"]) {
self.collectionVC = (CollectionVC *)[segue destinationViewController];
self.collectionVC.delegate = self;
}
if ([segue.identifier isEqualToString:#"table"]) {
self.tableVC = (TableVC *)[segue destinationViewController];
self.tableVC.delegate = self;
}
}
You must also implement the delegate methods in VCPrime, and declare CollectionDelegate, TableDelegate, or however you named them.
In CollectionVC, when someone selects something (or whatever), check that the delegate responds to your delegate method, then send that message:
if ([self.delegate respondsToSelector:#selector(doSomething)]) [self.delegate doSomething];
Then alter TableVC in the method that is called.
This is just a quick rundown. The internets are alive with great code examples.
I think I might have an improvement in Swift (the answer is old but I was facing this problem a day ago). I re-implemented the above in Swift, but with a twist. Instead of setting the VCPrime as a delegate for for both CollectionVC and TableVC, I would make the TableVC a delegate of CollectionVC. That is because CollectionVC needs to control TableVC. In general we can call a "master VC" the one that controls and the "delegate VC" the one that is controlled. In order to make sure that both VC are actually instantiated when I set one as the delegate of the other, I use Swift optionals and optional bindings. Here is a sample of code" (note that the master needs to have a "delegate" property and you might want to declare the appropriate protocols):
import UIKit
class ContainerController: UIViewController {
private var masterViewController: myMasterViewController?
private var delegateViewController: myDelegateViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "myMasterViewController" {
masterViewController = segue.destinationViewController as? myMasterViewController
} else if segue.identifier == "myDelegateViewController" {
delegateViewController = segue.destinationViewController as? myDelegateViewController
}
if let master = masterViewController, delegate = delegateViewController {
println("Master and delegate set")
master.delegate = delegate
}
}
}
I am developing an application that has several views and a root view controller. It's based on UINavigationController using pushViewController:
In didFinishLaunchingWithOptions:
BIDLoginController *loginController = [[BIDLoginController alloc] initWithNibName:#"Login" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:loginController];
[self.window setRootViewController:self.navController];
In loginController:
BIDMainPageController *mainPageController = [[BIDMainPageController alloc] initWithNibName:#"MainPage" bundle:nil];
[self.navigationController pushViewController:mainPageController animated:YES];
but the view controllers are subclasses of UIViewController, not UINavigationController. Now I want some of the views to support orientation(portrait and landscape) and some of them only support portrait. I have tried the following:
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
It doesn't work. shouldAutorotate: method never been called. I have tried UINavigationControllerDelegate with no result either. When I change these view controllers' super class from UIViewController to UINavigationController, the view doesn't show up, only a navigation bar is shown. Can anyone help with this?
You can't do what you are describing. iOS 6 doesn't support forcing different child view controllers of a UINavigationController to rotate. They can respond differently to device rotation, but they cannot be forced to rotate.
I have created new application using Master-Detail Templte with storyboard. I want to give user facility where they can Hide/Show Master View Controller in Landscape mode. I found few examples on net but none are using story board and Master-Detail template with Navigation Controller.
I have already implemented splitviewcontroller willHideViewController & willShowViewController which help me hide Master View Controller in Portrait mode. I am using below code in didFinishLaunchingWithOptions method of App Delegate to load views intially,
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
I would appriciate if you can point me right direction.
Thanks,
Tapan Desai
The master view is presented in a popover and the popover can be used to control the visibility of the master.
So...follow these steps:
1) Create a property to hold the popoverController
#property (nonatomic, strong) UIPopoverController *pc;
2) Capture the popoverController on the SplitViewController willHide delegate call
-(void) splitViewController:(UISplitViewController *)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)pc
{
barButtonItem.title = #"Menu";
id detailViewController = [self.splitViewController.viewControllers lastObject];
[detailViewController setSplitViewBarButtonItem:barButtonItem];
self.pc = pc; //poppver controller
}
3) Finally just use the pc var to dismiss the popover
if (self.pc) {
[self.pc dismissPopoverAnimated:YES];
}
I created an array of ImageViews in a Buttonclicked method:
UIImageView *arrayOfImageViews[10]
I successfully loaded them on the screen using a loop. (Big accomplishment for this beginner!)
But I want to refer to them in other methods (e.g. touchMove).
Where do I declare arrayOfImageViews to be both an array and a class of UIImageView so that I can use them in other methods? From what I can find, I'm to declare them in the .h and above Implementation in the .m. But I can't figure out the code and location to define the object as an array of UIImageViews in anything but the original function.
Try using a property. So in the .h file, you want to define the property something like this:
#interface MyClass: UIView {
NSArray *arrayOfImageViews;
}
#property (nonatomic,retain) NSArray *arrayOfImageViews;
Now in the implementation (.m file) You need to bind this property like this
#implementation MyClass
#synthesize arrayOfImageViews;
//now you need to initialize the array of images like this (look for the method viewDidLoad or //just add it)
- (void)viewDidLoad
{
NSMutableArray *tmpImages = [[NSMutableArray alloc]init];
//initialize images here
[self setArrayOfImageViews:tmpImages];
[tmpImages release];
[super viewDidLoad];
}
That should do it... Sorry I didn't have time to post code that actually compiles but it should help. Basically now you have a property on the class and you can access it like self.arrayOfImages, or even just arrayOfImages (from within MyClass). Also, I used NSArray which I suggest you do too. However you can also use UIImageView* arrayOfImages if you prefer.
I'm using PRISM 4 Navigation API with Unity in WPF. I have a tree-view that initiates a RequestNavigate passing in the selected tree node's ID (GUID).
_regionManager.RequestNavigate(RegionNames.DetailRegion,
ViewNames.SiteView + "?ID=" + site.ID);
In my module, I have registered the view/view-model like so:
_container.RegisterType<SiteDetailsViewModel>();
_container.RegisterType<object, SiteDetailsView>(ViewNames.SiteView);
When I select different nodes from the tree view, the DetailsRegion displays the SiteDetailsView as expected, but when I like to navigate back to the same node, a new view/view-model is created.
I tried to break at IsNavigationTarget(NavigationContext navigationContext) but this method appears to never be called.
Where have i gone wrong? Thanks in advance.
The problem was in such a place that I never expected... Debugging the Navigation API lead me to the RegionNavigationContentLoader
public object LoadContent(IRegion region, NavigationContext navigationContext)
When i stepped further down the code, I noticed a call to:
protected virtual IEnumerable<object> GetCandidatesFromRegion(
IRegion region,
string candidateNavigationContract)
I noticed that the naming here is key to matching the view to the view-model.
In my example, the name for each part was:
public class SiteDetailsViewModel { ... } // ViewModel
public class SiteDetailsView { ... } // View
ViewNames.SiteView = "SiteView" // ViewName constant
When I inadvertently made the following change:
ViewName.SiteView = "SiteDetailsView"
Everthing worked.
Conclusion
The name of the ViewModel must start
with the same name you used to
identify your view.
I tested this out by changing my view to:
public class MyView { ... }
and still using the same view name to register with the container and navigation:
_container.RegisterType<object, MyView>(ViewNames.SiteView);
...
_regionManager.RequestNavigate(RegionNames.DetailRegion,
ViewNames.SiteView + "?ID=" + site.ID);
This seems to work also. So it seems the name of the View-Model is intrinsically linked to the view name used to navigate to that view.
NOTE
This is only when you're using IoC and Unity with the PRISM 4 Navigation API. This doesn't seem to happen when using MEF.
Further Investigation
I am also aware that some guides have told us to use the typeof(MyView).FullName when registering the view with the Container...
_container.RegisterType<object, MyView>(typeof(MyView).FullName);
I personally think this is a mistake. By using the view's full name, you are creating a depending between the view and any one who wishes to navigate to that view...
_regionManager.RequestNavigate(RegionNames.DetailRegion,
typeof(MyView).FullName + "?ID=" + site.ID);
The registration of the View and the ViewModel is the problem. To have only one view you have to use a different lifetime manager. Without specifying a lifetime manager the TransientLifetimeManager is used which always returns a new instance on resolve. To have only one single instance you have to use the ContainerControlledLifetimeManager or the HierarchicalLifetimeManager:
_container.RegisterType<SiteDetailsViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<object, SiteDetailsView>(ViewNames.SiteView, new ContainerControlledLifetimeManager());