Why won't my custom view become First-Responder, iOS? - objective-c

I am following the book, iOS Programming Big Nerd Ranch Guide, and I have come to a lesson where I am to create a custom view, HypnosisView. Now, I am suppose to have this view change it's color on shake, but it says I am suppose to make it the First-Responder.
I used,
- (BOOL)canBecomeFirstResponder
{
return YES;
}
and
BOOL success = [view becomeFirstResponder];
if (success) {
NSLog(#"HypnosisView became the first responder"):
} else {
NSLog(#"Could not become first responder");
}
However, whenever I run my app, it always says that it could not become the first responder.
Any help would be appreciated.
UPDATE
I forgot to mention I get this output message.
Application windows are expected to have a root view controller at the end of application launch

Alright. I figured it out. I needed to put the delegate method
- (BOOL)canBecomeFirstResponder
{
return YES;
}
In the CustomView.m file, not my App Delegate file. Easy fix.

Related

Stop app going to background when swiping up from bottom edge on iPhone X iOS 12

My game is going into background mode when performing a swipe from the bottom edge of the screen on iPhone X iOS 12.
As per Apple documentation overriding preferredScreenEdgesDeferringSystemGestures and calling setNeedsUpdateOfScreenEdgesDeferringSystemGestures should stop the app from going to background but this is's not working on iOS 12.
I am using Unity3D and the editor has the Defer system gestures on edges option , which is implemented as per apple documentation, but also does not work.
I am compiling the project in Xcode 10.
Does anyone else have this problem and do you have a fix?
PS: I am testing this in an empty single view iOS project, the only added code is the following:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
return UIRectEdgeAll;
}
- (BOOL)prefersHomeIndicatorAutoHidden
{
return YES;
}
Update: Turns out that if I use a swift implementation it works. Too bad I cannot do this for the Unity3D 2017 generated project.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *){
setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
}
}
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge{
return [.all];
}
}
New Update: In Unity 2019 it works by unchecking "Status Bar Hidden" in Project Stttings\Resolution and presentation and making sure you check at least one edge in Poject Settings\Other Settings\Defer system gestures on edges
Removing prefersHomeIndicatorAutoHidden makes it work in Objective C also.
This is the working example implementation, for anyone having the same problem:
- (void)viewDidLoad {
[super viewDidLoad];
if (#available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
return UIRectEdgeAll;
}
And for those who, like me, are using Unity3d just delete the following method from UnityViewControllerBase+iOS.mm in the generated Xcode project:
- (BOOL)prefersHomeIndicatorAutoHidden
{
return YES;
}
As per the apple documentation, preferredScreenEdgesDeferringSystemGestures doesn't stop the app from going to background, it just gives your gesture precedence over system gesture.
However, if you try to do it successively a second time, the system gesture would work. You can easily verify this by comparing with other apps.
By default the line at the bottom which helps in swiping up is black in colour and the swipe up gesture would work instantly if you do not override this method. But in your app, the line will look gray'ed out initially. If you do a swipe up, it will become black again and if you swipe up a second time, the system gesture will work.
I am putting this as an answer because of limited characters for commenting.
For Swift the answer is to override the instance property like so within your UIViewController.
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
get { .all }
}
for example.

iOS 8 Orientation Change Detection

Running on iOS 8, I need to change the UI when rotating my app.
Currently I am using this code:
-(BOOL)shouldAutorotate
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation != UIInterfaceOrientationUnknown) [self resetTabBar];
return YES;
}
What I do it remove the current UI and add a new UI appropriate to the orientation. However, my issue is that this method is called about 4 times every time a single rotation is made.
What is the correct way to make changes upon orientation change in iOS 8?
Timur Kuchkarov is correct, but I'll post the answer since I missed his comment the first time I checked this page.
The iOS 8 method of detecting orientation change (rotation) is implementing the following method of the view controller:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
// Do view manipulation here.
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
Note: The controller's view has not yet transitioned to that size at this time, so be careful if your sizing code relies on the view's current dimensions.
The viewWillTransitionToSize:withTransitionCoordinator: method is called immediately before the view has transitioned to the new size, as Nick points out. However, the best way to run code immediately after the view has transitioned to the new size is to use a completion block in the method:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// your code here
}];
}
Thanks to the this answer for the code and to Nick for linking to it in his comment.

ios making a button change view when text field equals string

I am making this kind of password to skip levels in a game. But I dont know what code I need to make a button change View Controller...
- (IBAction)button:(id)sender {
if ([txt.text isEqualToString:#"passwordToSkipLevel1"]) {
// Code to change View Controller???
}
}
I am not sure if I have done it correct to far, and I am not sure how I can make it change View Controller either...
Any ideas?
Extra: Is there any method to make the password not case sensetive?
What you have seems fine as far as it goes. To make the comparison case-insensitive, you can use
if ([txt.txt localizedCaseInsensitiveCompare:#"passwordToSkipLevel1"] == NSOrderedSame) {
// ...
}
To change ViewController... is this a storyboard-based design? If so, you could write
if ([txt.txt localizedCaseInsensitiveCompare:#"passwordToSkipLevel1"] == NSOrderedSame) {
[self performSegueWithIdentifier:#"SegueToLevel2VC" sender:self];
}
where SegueToLevel2CV is the identifier you've given the segue that goes to the next level, typed in to Interface Builder when you constructed the storyboard.

How do I know if a view is visible or not?

Say I have two view controllers: xVC and yVC. I have used the shake API and and have used the methods -(void)motionBegan,-(void)motionEnded: and -(void)motionCancelled in xVC. What happens is when the device is shaken, it fires a simple animation. Now the thing is that this animation is fired even when the I have yVC open that is, when yVS.view has been added as the subview. What I am looking for is some if condition which I can use in -(void)motionEnded: like this:
if(yVC == nil)
{
//trigger animation
}
By that I mean that the shake shouldn't work when yVC is visible. How do I do that? Please help.
The general advice I have seen and used is to ask a view if it has a non-nil window property:
if( ! yVC.view.window) {
// trigger animation
}
But note that this doesn't always equate with being visible; though in most apps it's about as good as you can performantly get (the basic case where it's not accurate is when a different view completely obscures it, but this may still satisfy your needs)
Add this to both of your view controllers:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
visible = YES;
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
visible = NO;
}
Now, just check the variable isVisible of both the view controllers and trigger your animation likewise.
The previous answers all work to some degree, but fail to take modally presented view controllers into account. If view controller A presents view controller B, of the previous answers will tell you that A is still visible. If you, like me, want to know whether or not the view is actually visible (and not just a part of the view hierarchy), I would suggest also checking the presentedViewController property:
if (self.isViewLoaded && [self.view window] && !self.presentedViewController) {
// User is looking at this view and nothing else
}
This works since presentedViewController will be non-nil whenever the current view controller OR any of its ancestors are currently presenting another view controller.

QuickLook consumer as a delegate from an NSViewController

I am having some problems implementing QuickLook functionality from a table in an NSView. The limited documentation on QuickLook really doesn't help at all.
After reading through the Apple Docs (which are geared heavily towards custom generators and plugins), I ended up looking at the QuickLookDownloader sample code. This code is based upon a document-based application, but appears to be the right method for me (after all it is Apple's code and it does work in their project).
In my implementation I can get the QuickLook panel to show up just fine, and I can dismiss it just as easy. However, the panel itself never calls the delegate methods from within my NSViewController. As a result I never even get to displaying objects, just the wording "No items selected". And I am stumped.
I tried calling a setDelegate, but get warned about impending doom if I continue down that route...
[QL] QLError(): -[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
And then doom happens anyway with a dealloc when trying to respond to one of the delegate methods.
And yes I did read the header which confirms that I should be setting the delegate after I won the panel (see code below).
So here's my code, which pretty much matches the sample code with the exception of a) where I get my data from (I get it from an NSArrayController) and the b) where I get my preview item from (mine comes directly from my model object - or should anyway)
#interface MyViewController : NSViewController
<QLPreviewPanelDataSource, QLPreviewPanelDelegate> {
QLPreviewPanel * previewPanel;
NSArrayController * myArrayController;
NSTableView * myTable;
// [...] Other instance vars
}
#implementation MyViewController
// [...] all the other methods, init, dealloc etc...
-(IBAction)togglePreviewPanel:(id)previewPanel {
if ([QLPreviewPanel sharedPreviewPanelExists] &&
[[QLPreviewPanel sharedPreviewPanel] isVisible])
{
[[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
}
else
{
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
}
}
-(BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
{
return YES;
}
// This document is now responsible of the preview panel.
// It is allowed to set the delegate, data source and refresh panel.
-(void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
if (DEBUG) NSLog(#"QuickLook panel control did BEGIN");
previewPanel = [panel retain];
panel.delegate = self;
panel.dataSource = self;
}
// This document loses its responsisibility on the preview panel.
// Until the next call to -beginPreviewPanelControl: it must not change
// the panel's delegate, data source or refresh it.
-(void)endPreviewPanelControl:(QLPreviewPanel *)panel
{
[previewPanel release];
previewPanel = nil;
if (DEBUG) NSLog(#"QuickLook panel control did END");
}
// Quick Look panel data source
-(NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
{
if (DEBUG) NSLog(#"QuickLook preview count called");
return [[myArrayController selectedObjects] count];
}
-(id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel
previewItemAtIndex:(NSInteger)index
{
if (DEBUG) NSLog(#"QuickLook preview selection of item called");
return [[displayAC selectedObjects] objectAtIndex:index];
}
-(BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event {
if (DEBUG) NSLog(#"QuickLook panel error handler called");
// redirect all key down events to the table view
if ([event type] == NSKeyDown) {
[myTable keyDown:event];
return YES;
}
return NO;
}
The issue seems to be that the acceptsPreviewPanelControl never gets called, so the delegates never get used (they definitely never get called).
I'm sure this is a simple step that I'm missing, but after dissecting the sample code and scouring over the docs I don't see the answer.
Is it because this is all from within an NSViewController (although I see no reason why that should even come into the equation)?
Any and all help much appreciated.
SOLUTION UPDATE
Thanks to Peter's observation, the fix was a quick one. Don't you hate it when the error message in the debugger means what it says? :-)
In my class that loaded MyViewController I simply needed to add three lines of code to fix the problem.
// mainWindow is an IBOutlet to my window because the calling class
// is a simple object and not an NSWindowController otherwise I could
// have used `self` instead of `mainWindow`
NSResponder * aNextResponder = [mainWindow nextResponder];
[mainWindow setNextResponder:myViewControllerInstance];
[myViewControllerInstance setNextResponder:aNextResponder];
Job done :-) Thanks Peter.
Why would you expect it to send you delegate messages if you aren't (yet) its delegate? If you want it to send you delegate messages, then you need to set yourself as its delegate.
I tried calling a setDelegate, but get warned about impending doom if I continue down that route...
[QL] QLError(): -[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon. See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
“No controller”, it says. So, you need it to have a controller.
The comments on that header, particularly on acceptsPreviewPanelControl: and the QLPreviewPanel instance method updateController, suggest that the panel's controller, when it has one, is an object that is in the responder chain. Therefore, if your controller is not becoming the panel's controller, it's because your controller isn't in the responder chain.
So, fix that, and then it'll work.
I would imagine that your view controller should be in the responder chain whenever its view or any subview thereof is in the responder chain, but maybe this isn't the case. The documentation doesn't say. If all else fails, set yourself as some view's next responder explicitly (and its previous next responder as your next responder), then send the preview panel an updateController message.
After so many years, in the swift world, I found this line of code works as well.
Without rearrange the default response chain, just "push" your view controller to be the first responder in the window. I'm not sure if it works for every scenario:
view.window?.makeFirstResponder(self)
And the object setups are the same:
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
return true
}
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = self
panel.delegate = self
panel.currentPreviewItemIndex = //your initial index
}
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = nil
panel.delegate = nil
}