In my app I'm using a UISplitViewController. I wrote the code below to show a restaurant in the detail view.
- (void)tableView:(UITableView *)tableView didSelectRestaurant:(Restaurant *)restaurant {
UINavigationController *nvc = [[self.splitViewController viewControllers] objectAtIndex:1];
RestaurantsTabViewController *rtc = [[nvc viewControllers] objectAtIndex:0];
[rtc addTabWithRestaurant:restaurant];
}
This works fine on iPad, since it's rendering both the master and detail view. On iPhone it crashes on this line UINavigationController *nvc = [[self.splitViewController viewControllers] objectAtIndex:1]; though, because the detail view hasn't been rendered yet. How can I solve this?
Sorry for the Swift solution but I am pretty sure that you can adapt the code to your Objective-C environment. :)
Simply add a check to make sure that your rtc is not nil. If it is nil create a new instance and call the UISplitViewControllers showDetailViewController:sender: method:
func tableView(_ tableView: UITableView, didSelectRestaurant restaurant: Restaurant) {
if let detailNavigationController = splitViewController?.viewControllers.last as? UINavigationController,
let rtc = detailNavigationController.viewControllers.first as? RestaurantsTabViewController {
rtc.addTabWithRestaurant(restaurant)
} else {
let rtc = RestaurantsTabViewController()
rtc.addTabWithRestaurant(restaurant)
splitViewController?.showDetailViewController(rtc, sender: self)
}
}
UPDATE - Objective-C solution could look something like this:
- (void)tableView:(UITableView *)tableView didSelectRestaurant:(Restaurant *)restaurant {
if (self.splitViewController.viewControllers.count > 1) {
UINavigationController *nvc = [self.splitViewController.viewControllers objectAtIndex:1];
RestaurantsTabViewController *rtc = [nvc.viewControllers objectAtIndex:0];
[rtc addTabWithRestaurant:restaurant];
} else {
RestaurantsTabViewController *rtc = [RestaurantsTabViewController new];
[rtc addTabWithRestaurant:restaurant];
[self.splitViewController showDetailViewController:rtc sender:self];
}
}
Related
Im new to stack overflow and this is my first post so please be patient with me!
I have my sw_rear and sw_front set up and they work perfectly.
However when i tried creating a table view filled with prototype cells for my sw_right linked to an array seperate from the one i have in my sw_rear i found that when i tried swiping or even pushing a button to access the right menu the app crashes without any errors printing to the log.
(i have 2 arrays in 2 seperate swift files rearVc & rightVC linked to 2 seperate tableviews sw_rear & sw_right)
I have searched online for the last week (all my google hyperlinks are purple!!) and cant seem to find an answer at all. I thought that copying this code (found in my rear reveal table vc) into my right table vc would work but it unfortunately does not!
(CODED IN SWIFT, fyi i used a bridging header for swrevealvc as i dont know obj c)
import Foundation
class rearTableVC: UITableViewController {
var rearTableArray = [String]()
override func viewDidLoad() {
rearTableArray = ["a", "b", "c", "d", "e", "f"]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rearTableArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(rearTableArray[indexPath.row], forIndexPath: indexPath) as UITableViewCell
cell.detailTextLabel?.text = rearTableArray[indexPath.row]
return cell
}
}
as you can see im using "cell.detailTextLabel?.text" so that my customised prototype cells ive built in main.storyboard will be the ones populating my list.
Both are correctly linked to my nav controller as well as other scenes.
I know that part is fine, im just stuck with the code.
In my root view controller im running this code and it works like a charm so no problems there.
import Foundation
class aVC : UIViewController {
#IBOutlet var menuButtonLeft: UIBarButtonItem!
#IBOutlet var menuButtonRight: UIBarButtonItem!
override func viewDidLoad() {
menuButtonLeft.target = self.revealViewController()
menuButtonLeft.action = #selector(SWRevealViewController.revealToggle(_:))
menuButtonRight.target = self.revealViewController()
menuButtonRight.action = #selector(SWRevealViewController.rightRevealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
Any help is greatly appreciated and i am more then willing to add any more info that is needed!!
Thanks.
(sorry i cant add pictures)
SOLUTION:
Hi after beating around the bush for hours i finally found a solution, a very very excruciatingly simple solution.
In the SWRevealViewController.m three important parts are missing. I don't know obj-c at all but figured that these lines were missing code for the rightViewController so after adding them everything worked!
- (id)init
{
return [self initWithRearViewController:nil frontViewController:nil rightViewController:nil];
}
- (id)initWithRearViewController:(UIViewController *)rearViewController frontViewController:(UIViewController *)frontViewController rightViewController:(UIViewController *)rightViewController;
{
self = [super init];
if ( self )
{
[self _initDefaultProperties];
[self _performTransitionOperation:SWRevealControllerOperationReplaceRearController withViewController:rearViewController animated:NO];
[self _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:frontViewController animated:NO];
[self _performTransitionOperation:SWRevealControllerOperationReplaceRightController withViewController:rightViewController animated:NO];
}
All i added was the
return [self initWithRearViewController:nil frontViewController:nil rightViewController:nil];
and
rightViewController:(UIViewController *)rightViewController;
and
_performTransitionOperation:SWRevealControllerOperationReplaceRightController withViewController:rightViewController animated:NO];
lines
EDIT 2 (requested info)
#pragma mark - Init
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if ( self )
{
[self _initDefaultProperties];
}
return self;
}
- (id)init
{
return [self initWithRearViewController:nil frontViewController:nil rightViewController:nil];
}
- (id)initWithRearViewController:(UIViewController *)rearViewController frontViewController:(UIViewController *)frontViewController rightViewController:(UIViewController *)rightViewController;
{
self = [super init];
if ( self )
{
[self _initDefaultProperties];
[self _performTransitionOperation:SWRevealControllerOperationReplaceRearController withViewController:rearViewController animated:NO];
[self _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:frontViewController animated:NO];
[self _performTransitionOperation:SWRevealControllerOperationReplaceRightController withViewController:rightViewController animated:NO];
}
return self;
}
I want to include some UIKeyCommands in my app. My app consists of one UISplitViewController that forces the master to be always visible on iPad full screen. On smaller screen it works like it normally would.
Now, I've implemented some UIKeyCommands in the MasterViewController and some in the DetailViewController. However, the app will only show those in DetailViewController. So I put all of them in the RootSplitViewController, but that will show all of them, even when the MasterViewController is hidden in iOS 9's splitview.
What I want though, is for it to show all when the app is fullscreen on iPad and thus the MasterViewController is forced on screen together with the DetailViewController. And when the view is small (ie 50-50) and the MasterViewController is hidden, I want it to only show those of the window that's on screen.
Any ideas on how to achieve this?
In the end I managed to do this - although in a not-so-pretty way.
The UIKeyCommands are added to the RootSplitViewController.
- (NSArray *)keyCommands {
if (self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
return #[
[UIKeyCommand keyCommandWithInput:#"r" modifierFlags:UIKeyModifierCommand action:#selector(changeRestaurant:) discoverabilityTitle:#"Change restaurant"],
[UIKeyCommand keyCommandWithInput:#"t" modifierFlags:UIKeyModifierCommand action:#selector(changeTable:) discoverabilityTitle:#"Change table"]
];
} else {
if (self.masterIsVisible == YES) {
return #[
[UIKeyCommand keyCommandWithInput:#"t" modifierFlags:UIKeyModifierCommand action:#selector(changeRestaurant:) discoverabilityTitle:#"Change restaurant"]
];
} else {
return #[
[UIKeyCommand keyCommandWithInput:#"t" modifierFlags:UIKeyModifierCommand action:#selector(changeTable:) discoverabilityTitle:#"Change table"]
];
}
}
}
Those methods call the actual methods in the specific UIViewController.
- (void)changeRestaurant:(id)sender {
UINavigationController *nav = (UINavigationController *)[self.viewControllers objectAtIndex:0];
RestaurantController *master = [nav.viewControllers objectAtIndex:0];
[master changeRestaurant];
}
- (void)changeTable:(id)sender {
UINavigationController *nav = (UINavigationController *)[self.viewControllers objectAtIndex:1];
TableController *detail = [nav.viewControllers objectAtIndex:0];
[detail changeTable:sender];
}
In order for this to work I added a BOOL to the UISplitViewController.
#interface RootSplitViewController : UISplitViewController
#property (nonatomic) BOOL masterIsVisible;
#end
Which is then called in the MasterViewController.
- (void)viewDidDisappear:(BOOL)animated {
RootSplitViewController *rootView = (RootSplitViewController *)self.splitViewController;
rootView.masterIsVisible = NO;
}
- (void)viewDidAppear:(BOOL)animated {
RootSplitViewController *rootView = (RootSplitViewController *)self.splitViewController;
rootView.masterIsVisible = YES;
}
I know this might not be the pretties method, but it works. If anyone knows a better way to do it, I'd love to hear your feedback.
Is there a way I could create gui for ios using like xml in android. Storyboard is so heavy and make my computer unresponsive. I can't find any decent tutorial for creating gui through coding it
Well storyboard is an XML file only. As you can see by right clicking on it and opening it as a source file.
You can create a storyboard and open it as a source file and it will open as an XML file. But mind it, its not a single Scene, its the whole scene of your application as a single XML file.
So you can configure it like that.
first try with .XIB. it gives you amazing features of auto-layout functionality.
or you can create any view with code. eg. if you are using swift you can create your view like this.
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
self.navigationController?.navigationBarHidden = true
/////creating navigation bar//////////
var _customNavView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 64))
_customNavView.backgroundColor = headerColor
self.view.addSubview(_customNavView)
var _lblMapTitle = UILabel(frame: CGRectMake(_customNavView.center.x-75,self.view.frame.origin.y+30,150,30))
_lblMapTitle.text = "SETTINGS"
_lblMapTitle.textColor = UIColor.whiteColor()
_lblMapTitle.font = UIFont(name: "ErasITC-Demi", size: 18)
_lblMapTitle.textAlignment = .Center
_customNavView.addSubview(_lblMapTitle)
_imgCoverPic.frame = CGRectMake(0, _customNavView.frame.origin.y+_customNavView.frame.height, self.view.frame.size.width,160)
_imgCoverPic.contentMode = .ScaleAspectFill
_imgCoverPic.alpha = 0.5
_imgCoverPic.clipsToBounds = true
self.view.addSubview(_imgCoverPic)
_imgPicturePic.frame = CGRectMake(self.view.frame.width/2-50, _imgCoverPic.frame.origin.y+30, 110 ,110)
_imgPicturePic.clipsToBounds = true
_imgPicturePic.layer.borderColor = UIColor.whiteColor().CGColor
_imgPicturePic.layer.borderWidth = 3
_imgPicturePic.layer.cornerRadius = _imgPicturePic.frame.width/2
self.view.addSubview(_imgPicturePic)
////////creating table view///////////
var heightForTab :CGFloat = AppDelegate.sharedInstance.heightForTabBar as! CGFloat
//println(heightForTab)
get_all_status_value_for_user()
_tblSettings.frame = CGRectMake(0,_imgCoverPic.frame.origin.y+_imgCoverPic.frame.size.height+15, self.view.frame.size.width, self.view.frame.size.height-heightForTab-64)
_tblSettings.separatorStyle = UITableViewCellSeparatorStyle.None
_tblSettings.showsVerticalScrollIndicator = false
// _tblSettings.rowHeight = UITableViewAutomaticDimension
// _tblEvent.backgroundColor = UIColor.redColor()
self.view.addSubview(_tblSettings)
}
As Mohit tomar answer states, i would suggest using just xibs instead, they are a bit more light weight to edit. But what I would personally do, because storyboards give more functionality than xibs, is to break up your storyboard into very small storyboards, no more than 3 viewcontrollers on one. Then in code, if you need to segue to a viewcontroller on another storyboard, you can just load the storyboard manually and then segue to a particular viewcontroller on it programatically.
Its even possible to make your own custom segue that will segue to a view controller on another storyboard without having to write much code
#interface StoryboardLinkSegue () {
NSString *segueType;
}
#end
#implementation StoryboardLinkSegue
+ (UIViewController *)viewControllerNamed:(NSString *)identifier
{
NSArray *info = [identifier componentsSeparatedByString:#"#"];
NSString *storyboardName = info[1];
NSString *viewControllerName = info[0];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName
bundle:nil];
UIViewController *scene = nil;
if (viewControllerName.length == 0 || [viewControllerName isEqualToString:#"default"]) {
scene = [storyboard instantiateInitialViewController];
}
else {
scene = [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
}
return scene;
}
- (id)initWithIdentifier:(NSString *)identifier
source:(UIViewController *)source
destination:(UIViewController *)destination
{
NSArray *info = [identifier componentsSeparatedByString:#"#"];
if(info.count > 2){
segueType = [info[2] lowercaseString];
}
else {
segueType = #"push";
}
return [super initWithIdentifier:identifier
source:source
destination:[StoryboardLinkSegue viewControllerNamed:identifier]];
}
- (void)perform
{
if([segueType isEqualToString:#"push"]){
[[self.sourceViewController navigationController] pushViewController:self.destinationViewController animated:YES];
}
else if ([segueType isEqualToString:#"modal"]) {
[self.sourceViewController presentViewController:self.destinationViewController animated:YES completion:nil];
}
}
now if you make a segue to an empty viewcontroller in your storyboard, just put the segue identifier as <storyboardName>#<viewControllerName> and to be technical you can use <storyboardName>#<viewControllerName>#modal or <storyboardName>#<viewControllerName>#push to make it a modal or push segue. This will make it segue from one storyboard to another without having to write any code (besides copy pasting the code ive given you)
example: mainStoryboard#loginViewController
will go to the viewcontroller named loginViewController that is in the mainStoryboard
you just need to give all your viewcontrollers names in your storyboards under the identity inspector -> Storyboard ID
So I have my AppDelegate method trying to set an object in a ViewController method. My AppDelegate looks like this:
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
//Grab a reference to the UISplitViewController
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
PatientDetailViewController* patientDetailViewController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = patientDetailViewController;
//Grab a reference to the masterviewcontroller and get the first patient in the list.
UINavigationController *patientNavController = [splitViewController.viewControllers objectAtIndex:0];
PatientMasterTableViewController *patientMasterViewController = (PatientMasterTableViewController *)[patientNavController topViewController];
Patient* firstPatient = [[patientMasterViewController patientArray] objectAtIndex:0];
//Set it as the detailviews patient.
[patientDetailViewController setPatient:firstPatient];
//Set the detail's as the left's delegate.
patientMasterViewController.delegate = patientDetailViewController;
}
return YES;
}
and the method to set the object looks like this:
-(void)setPatient:(Patient *)patient
{
if (![self.patient isEqual:patient])
{
self.patient = patient;
//Update the UI to reflect the new patient selected from the list.
[self refreshUI];
}
}
The issue that I'm having is that the setPatient method will be called non stop until the program crashes and I have no idea why. Can anyone shed some light on this?
This:
-(void)setPatient:(Patient *)patient
{
if (![self.patient isEqual:patient])
{
self.patient = patient;
//Update the UI to reflect the new patient selected from the list.
[self refreshUI];
}
}
Should be:
-(void)setPatient:(Patient *)patient
{
_patient = patient;
[self refreshUI];
}
I have a view controller that's instantiated from IB. It contains a UIButton whose action creates a UIPopoverController whose delegate updates the title of the UIButton through:
- (void) popoverSelected:(NSString*)string {
[self.sortButton setTitle:string forState:UIControlStateNormal];
[self.sortPickerPopover dismissPopoverAnimated:YES];
}
popoverSelected is a delegate method for the UIPopoverController, which contains a simple UITableView.
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedSort = [_sortTypes objectAtIndex:indexPath.row];
if (_delegate != nil) {
[_delegate popoverSelected:selectedSort];
}
}
The popover is instantiated by the TouchUpInside action on the self.button through:
- (IBAction)sortButtonPressed:(id)sender {
if (_sortPicker == nil) {
// Create the picker view controller
_sortPicker = [[SortPickerViewController alloc] initWithStyle:UITableViewStylePlain];
// Set this as the delegate
_sortPicker.delegate = self;
}
if (_sortPickerPopover == nil) {
// The colour picker popover is not showing. Show it
_sortPickerPopover = [[UIPopoverController alloc] initWithContentViewController:_sortPicker];
[_sortPickerPopover presentPopoverFromRect:_sortButton.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
} else {
// if it's showing, we want to hide it
[_sortPickerPopover dismissPopoverAnimated:YES];
_sortPickerPopover = nil;
}
}
This has no issues the first time the button's title is updated, but second time around I get an EXC_BAD_ACCESS when executing setTitle: in popoverSelected.
I can't see anywhere that I'm releasing the button accidentally (and the object definitely still exists at this point). The project is using ARC.
With NSZombies I've occasionally reached [__NSArrayI valueRestriction] unrecognised selector sent to instance which makes even less sense.
Are there any obvious approaches I can take to debug this further?
Instead of checking _sortPickerPopover == nil to know whether to show it, you should check [_sortPickerPopover isPopoverVisible]. Also, I would put the construction code into autoloaders.
- (UIPopoverController *)sortPickerPopover
{
if (!_sortPickerPopover) {
_sortPickerPopover = [[UIPopoverController alloc] initWithContentViewController:self.sortPicker];
}
return _sortPickerPopover;
}
- (SortPickerViewController *)sortPicker
{
if (!_sortPicker) {
_sortPicker = [[SortPickerViewController alloc] initWithStyle:UITableViewStylePlain];
// Set this as the delegate
_sortPicker.delegate = self;
}
return _sortPicker;
}
- (IBAction)sortButtonPressed:(UIButton *)sender
{
if ([self.sortPickerPopover isPopoverVisible]) {
[self.sortPickerPopover dismissPopoverAnimated:YES];
} else {
[self.sortPickerPopover presentPopoverFromRect:sender.frame
inView:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
/***
* NOTE: Delegate methods should always pass the calling object as the first
* object. Additionally, the name is not very descriptive of what is actually
* being performed and does not use should/will/did naming conventions.
* You should consider changing this method to something like:
* - (void)sortPickerViewController:(SortPickerViewController *)sortPicker
* didSelectSortMethod:(NSString *)sortMethod
**/
- (void)popoverSelected:(NSString *)string
{
[self.sortButton setTitle:string forState:UIControlStateNormal];
[self.sortPickerPopover dismissPopoverAnimated:YES];
}
Once these changes are made, the only other possible source of problems is the implementation of your SortPickerViewController. I'll look that over for you if you can post that view controller as well.