I want to ask about the proper implementation of 3D Touch. For example there UIViewController, which loads different from each other depending on the data being openly application. If as usual, it shows the same data, if in 3D Touch, then the other. I've done through NSUserDefaults kept to a variable, that is, if the normal start is false, if in 3D Touch true. I do more with NSNotificationCenter. It all worked, but it is not the correct implementation of this task. How best do I do that?
Please try following code for open detail view using 3D touch.
IBOutlet UIButton *btnDetail;
#pragma mark - Previewing delegate
- (UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location
{
UIViewController *detailVC = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
detailVC.preferredContentSize = CGSizeMake(0.0, 568.0);
previewingContext.sourceRect = self.btnDetail.frame;
return detailVC;
}
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit{
[self showViewController:viewControllerToCommit sender:self];
}
Related
I want to add 3d touch home screen shortcuts to my objective c app. The main part of my app functions when a UIButton is pressed. This calls the method in the ViewController.h and ViewController.m
- (IBAction)StartScanning:(id)sender;
This allows the camera to start functioning and pushes the correct view controller and desired methods.
I have read many walkthroughs but still cannot understand how to start this IBAction when a 3d touch shortcut is pressed.
Sorry if this is a repeat question or I am just being stupid. I'm a bit new to all of this.
It's not that hard to do and as you mentioned, there's a lot of tutorials out there to help. In summary, first you need to add the 3d touch delegate to your ViewController.h or .m. <UIViewControllerPreviewingDelegate>
That will give you access to the delegate methods you need to show the home screen shortcuts. Here's an example of one of my apps (name removed in this example).
In my AppDelegate, performActionForShortcutItem:completionHandler: is called first and sent the shortcut the user selected. Use it to determine how to respond to the shortcut. I passed the shortcut to a method, handleShortcutItem:shortcutItem" that would determine which storyboard I would use (I know there's no 3D Touch in iPads right now but I wanted to build in the code for when Apple comes out with one).
Based on the shortcut, I create my ViewController and pass the shortcut to the method logShortcutUsed, passing in the shortcut title.
#pragma mark - Shortcut Items
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
completionHandler([self handleShortcutItem:shortcutItem]);
}
- (BOOL)handleShortcutItem:(UIApplicationShortcutItem *)shortcutItem {
UIStoryboard *storyboard;
UINavigationController *navController = (UINavigationController *) self.window.rootViewController;
if (IS_IPAD()) {
storyboard = [UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil];
} else {
storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
}
xxxViewController *vb = (xxxViewController *)navController.topViewController;
if ([shortcutItem.localizedTitle isEqualToString:#"New Match"]) {
[vb logShortcutUsed:shortcutItem.localizedTitle];
[vb startNewMatch];
return TRUE;
} else if ([shortcutItem.localizedTitle isEqualToString:#"New Game"]) {
[vb logShortcutUsed:shortcutItem.localizedTitle];
[vb gamePressedFromShortcut];
return TRUE;
}
return FALSE;
}
In my main ViewController, I create the dynamic shortcuts (you can have static or dynamic shortcuts). This is what will be seen by the user when they 3D Touch the icon. I include an icon as well, that's optional. The shortcutItems is just an array of UIApplicationShortcutItems.
- (void)setupDynamicShortcuts {
UIApplicationShortcutItem *newMatch = [[UIApplicationShortcutItem alloc] initWithType:#"$(PRODUCT_BUNDLE_IDENTIFIER).NewMatch"
localizedTitle:NSLocalizedString(#"New Match", #"Start a new match")
localizedSubtitle:NSLocalizedString(#"Start a new match", #"Start a new match button.")
icon:[UIApplicationShortcutIcon iconWithTemplateImageName:#"Sport Net-50"]
userInfo:nil];
UIApplicationShortcutItem *newGame = [[UIApplicationShortcutItem alloc] initWithType:#"$(PRODUCT_BUNDLE_IDENTIFIER).NewGame"
localizedTitle:NSLocalizedString(#"New Game", #"Start a new game")
localizedSubtitle:NSLocalizedString(#"Start a new game", #"Start a new game button.")
icon:[UIApplicationShortcutIcon iconWithTemplateImageName:#"volleyball-50"]
userInfo:nil];
[UIApplication sharedApplication].shortcutItems = #[newMatch, newGame];
}
In the same ViewController are the methods that will be called from the AppDelegate, startNewMatch and gamePressedFromShortcut. I also log these calls to my analytics so I can track how many times people use this feature, which is something I strongly suggest.
It's not as difficult as it initially seems.
Sorry for the long-winded explination, but this question - or something similar - has been asked a few times and I havent found a satisfactory answer. I am writing an iPad app in iOS 8 that implements UISplitViewController. Recently I have been attempting to get it to work on the iPhone. It transferred over pretty well, everything collapses automatically and a back button is included in the left side of my nav. bar.
My problem is that I want to keep the back button functionality to pop one view off the stack, but also be able to pan back to the primary view even if there are several detail views on top of it. Ideally, I want to be able to overwrite or redirect the interactivePopGestureRecognizer so that the gesture smoothly pans to the primary view (in some cases it can have anywhere from 1 to 4 detail views stacked on top of it). But, I cannot figure out how to do this.
My current solution (code below) is to disable the interactivePopGestureRecognizer in the detail viewcontroller and implement my own ScreenEdgePanGestureRecognizer that, when triggered, executes popToRootViewController. I've subclassed the ScreenEdgePanGestureRecognizer so it treats the screen edge pan as a discrete "swipe" (i.e. once a large enough screen edge swipe is detected - pop everything off the stack so the primary view is visible).
Code in detail view controller to stop interactivePopGestureRecognizer:
-(void)viewWillAppear : (BOOL) animated {
[super viewWillAppear : animated];
// stops navigation controller from responding to the default back swipe gesture
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled =NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
// Disable the default back swipe gesture tied to automatically included back button
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isEqual:self.navigationController.interactivePopGestureRecognizer]) {
return NO;
} else {
return YES;
}
}
I didn't think it was necessary to include my subclass for the screenEdgePanGestureRecognizer because it has nothing to do with the solution I am asking about here is some pseudocode that shows what my #selector does in the detail viewcontroller:
- (IBAction)leftEdgeSwipe:(ScreenEdgeSwipeGestureRecognizer*)sender {
if (sender.swipeIsValid) {
[(UINavigationController *)self.splitViewController.viewControllers[0]
popToRootViewControllerAnimated:YES];
}
}
I tried to use the continuous pan, but cannot find a way to present the primary view in the background as I am pulling the current view aside to give that clean, smooth panning effect. I am able to make it so I can move the current view around, but there is just a grey background behind it where I would want my primary view to be.
Summation: If there is indeed no way to change the interactivePopGestureRecognizer to always jump to my primary view (ideal solution), then any info on how I can make my own smooth pan back to my primary view would be much appreciated.
So I have been messing around with making a smooth panning gesture subclass. Currently it functions similarly to Apple's back gesture except it jumps all the way back to the root view controller instead of popping one view off the stack. The only problem is that it does not yet show the primary view in the background while panning. I will update the answer once I get that worked out.
Here is the subclass:
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "ScreenEdgeSwipeGestureRecognizer.h"
#interface ScreenEdgeSwipeGestureRecognizer ()
#property (nonatomic) UINavigationController* navController;
#end
#implementation ScreenEdgeSwipeGestureRecognizer{
CGPoint _screenCenter;
CGPoint _cumulativePanDistance;
}
- (id)initWithNavigationController:(UINavigationController*)navController {
self = [super initWithTarget:self action:#selector(leftEdgePan:)];
_screenCenter = CGPointZero;
_cumulativePanDistance = CGPointZero;
self.edges = UIRectEdgeLeft;
self.navController = navController;
return self;
}
- (IBAction)leftEdgePan:(ScreenEdgeSwipeGestureRecognizer*)sender {
assert(sender == self);
switch (self.state) {
case UIGestureRecognizerStateBegan:
[self initializePositions];
break;
case UIGestureRecognizerStateChanged:
[self updatePositions];
break;
case UIGestureRecognizerStateEnded:
[self animateViewBasedOnCurrentLocation];
break;
case UIGestureRecognizerStateCancelled:
[self animateViewToCenter];
break;
default:
break;
}
// Reset velocity of the pan so current velocity does not compound with velocity of next cycle
[sender setTranslation:CGPointMake(0, 0) inView:sender.view];
}
- (void)initializePositions {
_screenCenter = self.view.center;
_cumulativePanDistance = CGPointZero;
}
- (void)updatePositions {
// Track position of user touch event
CGPoint deltaSinceLastCycle = [self translationInView:self.view];
// View center = view center at last cycle + distance moved by user touch since last cycle
self.view.center=CGPointMake((self.view.center.x + deltaSinceLastCycle.x), self.view.center.y+ 0);
// Update the total positive distance traveled by the user touch event.
_cumulativePanDistance.x = _cumulativePanDistance.x + deltaSinceLastCycle.x;
}
- (void)animateViewBasedOnCurrentLocation {
if (_cumulativePanDistance.x >= (_screenCenter.x - 50)){
[self reset];
[_navController popToRootViewControllerAnimated:YES];
}else{
[self animateViewToCenter];
[self reset];
}
}
- (void)animateViewToCenter {
[UIView animateWithDuration:0.25 animations:^{self.view.center = self->_screenCenter;}];
}
- (void)reset {
[super reset];
_cumulativePanDistance = CGPointZero;
self.state = UIGestureRecognizerStatePossible;
}
#end
Here is how I instantiate the recognizer in my view controller:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Initialize the screen edge pan gesture recognizer.
_masterNavigationController = self.splitViewController.viewControllers[0];
ScreenEdgePanGestureRecognizer* edgePanRecognizer = [[ScreenEdgeSwipeGestureRecognizer alloc] initWithNavigationController:_masterNavigationController];
// Add recognizer to view this controller is bound to.
[self.view addGestureRecognizer:_edgePanRecognizer];
}
I found a really good walk through of how to pass string values back from a ViewController to a calling ViewController and got it working perfectly. The example is really very good.
https://www.youtube.com/watch?v=YVikeoR3gYg
That said, the technique for passing back content seems relatively straight forward now that I have seen it, even if it's not that intuitive.
The example code however only includes two controllers. When I replicated the code using a much more detailed Storyboard, the code simply doesn't work. In my test app, I even embedded the calling Controller inside a NavigationController to see whether this would have an affect, but it still continued to work fine.
In my application, the ViewController is embedded within a NavigationController that is called via a SWRevealController segue class. I don't know if this is important or relevant but I am mentioning it.
I then call a CollectionViewController to choose an icon that should be passed back to the calling ViewController.
When I select the icon, I correctly identify the icon and pop
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedIcon = [placeIcons objectAtIndex:indexPath.row];
NSLog(#"In IconCollectionViewControlled - selected %#", selectedIcon);
NSString *itemToPassBack = #"12345"; // Just testing any old string here...
// [self.delegate passBackIcon:selectedIcon]; // commenting out while testing
[self.delegate passBackIcon:itemToPassBack];
[self.navigationController popViewControllerAnimated:YES];
}
I get a correct trace suggesting that the right icon is selected. I would then expect that the text '12345' would be passed back to the calling Controller.
In my calling Controller, I have the following:
- (void)passBackIcon:(NSString *)iconName {
NSLog(#"Icon to use is %#", iconName);
}
But this just isn't being called at all (or at least I'm not seeing the NSLog being shown. It's just being ignored.
The delegate is being correctly declared as far as I can tell.
Am I missing something?
assuming you are working with segues, in the method prepareSegue you should setting the delegate
for Example :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"YOUR_SEGUE_IDENTIFIER"] ) {
DestinationVc *vc = (DestinationVc *)segue.destinationViewController;
[vc setDelegate:self];
}
}
Hope it works for you
I've found this to be the easiest way to pass string and other information around using a tableView.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ViewControllerYouWantToPassToo *result = [self.storyboard instantiateViewControllerWithIdentifier:#"NameOfTheViewController"];
result.stringName = #"12345" // String Name is a NSString property you set up in the ViewController you want to pass too
[self.navigationController pushViewController:result animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I suggest you wrapping your delegate in a check to see that it is valid and that it has adopted the respective method (if optional).
if(self.delegate && [self.delegate respondsToSelector:#selector(passBackIcon:)]){
[self.delegate passBackIcon:itemToPassBack];
}else{
NSLog(#"Your delegate is not setup correctly");
}
If it enters the else, you have not set the delegate properly..ie you likely never did
self.delegate = SomeInstanceOfAClassThatAdoptsYourDelegate;
I'm developing an app which uses splitView.
I'm using two items in splitView (Called them Customer and Supplier).
When I click on one of them I just use one viewController to display (called it: ContactViewController) and I use collectionView to display its data. To get data I just code it:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!dbManager.synchronized) {
if (contactType == ContactTypeCustomer)
[dbManager requestData:kDbCustomers predicate:nil target:self];
else if (contactType == ContactTypeSuppplier)
[dbManager requestData:kDbSuppliers predicate:nil target:self];
}
}
And when get successful:
#pragma mark
#pragma DBDelegate
- (void)requestDataCompleted:(NSMutableArray *)results
{
datasource = results;
[self.collectionView reloadData];
}
I use I3DragBetweenHelper downloaded from github.com
Embedded into my app to initial drag and drop. To do this, I call the below method into viewDidLoad of ContactViewController
- (void) initDragAndDrop
{
self.helper = [[I3DragBetweenHelper alloc] initWithSuperview:self.view
srcView:_collectionView
dstView:_collectionView];
self.helper.delegate = self;
self.helper.isDstRearrangeable = NO;
self.helper.isSrcRearrangeable = NO;
self.helper.doesSrcRecieveDst = NO;
self.helper.doesDstRecieveSrc = YES;
self.helper.hideDstDraggingCell = YES;
self.helper.hideSrcDraggingCell = NO;
}
The helper here is:
#property (strong, nonatomic) I3DragBetweenHelper *helper;
The problem is when I click on Supplier I can drag and drop the cell of collectionView into ContactViewController. Below method had called and worked:
- (BOOL)droppedOutsideAtPoint:(CGPoint)pointIn fromDstIndexPath:(NSIndexPath *)from
But when I click on Customer the above method doesn't call. I can't even drag my cell of collectionView into ContactViewController. Any help will appreciate.
Why is isDstRearrangeable set to NO ??? coz eventually you would grab something and drop it onto something ( dest ) and that something ( dest ) will be rearranged, am i wrong ??
does your drag and drop happen in the same place ?? ( i.e: UICollectionView )
if it does, then why do you have src as not rearrangeable ??? both your destination and src should be rearrangeable.
Try this and let me know how it goes :)
I dont know about this library but I think the problem is your srcView and dstView are the same. These should be different and must be either UITableView or UICollectionView.
If you are using UISplitViewController, its probably going to be the Master and Detail View Controllers used by the UISplitView
i'm used to programming for iOS, and I've become very accustomed to the UIViewController. Now, i'm creating an OSX application and i'm having a few general questions on best practice.
In a UIViewController I generally setup my views in the -(void)viewDidLoad method - I don't actually create a custom UIView for the UIViewController unless it's really needed - so the UIViewController adds view to its own view, removes them, animates them and so forth - first off, is good practice?
And for my main question - what is the best practice in OSX? I like creating interfaces programatically and simply prefer it that way. If i, say create a new custom window and want to manage its view. What's the best way to do it, and where to i instantiate the user interface best?
Summary: How do i construct custom views programatically and set up a best-practice relationship between views and controllers in OSX? And is it considered good practice to use a view controller to create the views within its view?
Kind regards
To construct the view in code in an NSViewController, override loadView and be sure to set the view variable. Do not call super's implementation as it will attempt to load a nib from the nibName and nibBundle properties of the NSViewController.
-(void)loadView
{
self.view = [[NSView alloc] init];
//Add buttons, fields, tables, whatnot
}
For a NSWindowController, the procedure is very similar. You should call windowDidLoad at the end of your implementation of loadWindow. Also the window controller does not call loadWindow if the window is nil, so you will need to invoke it during init. NSWindowController seems to assume you will create the window in code before creating the controller except when loading from a nib.
- (id)initWithDocument:(FFDocument *)document
url:(NSURL *)url
{
self = [super init];
if (self)
{
[self loadWindow];
}
return self;
}
- (void)loadWindow
{
self.window = [[NSWindow alloc] init];
//Content view comes from a view controller
MyViewController * viewController = [[MyViewController alloc] init];
[self.window setContentView:viewController.view];
//Your viewController variable is about to go out of scope at this point. You may want to create a property in the WindowController to store it.
[self windowDidLoad];
}
Some optional fancification (10.9 and earlier)
Prior to 10.10, NSViewControllers were not in the first responder chain in OSX. The menu will automatically enable/disable menu items for you when an item is present in the responder chain. You may want to create your own subclass of NSView with an NSViewController property to allow it to add the controller to the responder chain.
-(void)setViewController:(NSViewController *)newController
{
if (viewController)
{
NSResponder *controllerNextResponder = [viewController nextResponder];
[super setNextResponder:controllerNextResponder];
[viewController setNextResponder:nil];
}
viewController = newController;
if (newController)
{
NSResponder *ownNextResponder = [self nextResponder];
[super setNextResponder: viewController];
[viewController setNextResponder:ownNextResponder];
}
}
- (void)setNextResponder:(NSResponder *)newNextResponder
{
if (viewController)
{
[viewController setNextResponder:newNextResponder];
return;
}
[super setNextResponder:newNextResponder];
}
Finally, I use a custom NSViewController that overrides setView to set the viewController property when I use my custom views.
-(void)setView:(NSView *)view
{
[super setView:view];
SEL setViewController = #selector(setViewController:);
if ([view respondsToSelector:setViewController])
{
[view performSelector:setViewController withObject:self];
}
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
And for my main question - what is the best practice in OSX? I like
creating interfaces programatically and simply prefer it that way. If
i, say create a new custom window and want to manage its view. What's
the best way to do it, and where to i instantiate the user interface
best?
All these are done in awakeFromNib and init.
The following code is creating many windows and storing them in array. For each window you can add views. And each view may contains all the controls you wish to have.
self.myWindow= [[NSWindow alloc] initWithContentRect:NSMakeRect(100,100,300,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.myWindowArray addObject:self.myWindow];
for (NSWindow *win in self.myWindowArray) {
[win makeKeyAndOrderFront:nil];
}