The user drags the game piece (the i-th image view) to its target location. The program counts, sees that all the items are in the correct places, announces "Game Over" and sets userInteractionEnabled to NO.
Great, except that if the user's finger is still down on the game piece, the user can drag the piece back out of the target area by accident. "Game Over" is showing but the piece is no longer in the correct place.
Is there a way to force a touchesEnded (not detect a touchesEnded) so that the contact with the game piece is (effectively) broken when the piece is in its final destination (i.e. so that the user can't accidentally pull it out of position)?
userInteractionEnabled = NO does not seem to take effect until the touch is released.
I'm not aware of a way to force touchesEnded.
Obviously one way to handle this is for your application to maintain state that indicates that the game is over and guard against moving any game pieces when in that state.
You might try beginIgnoringInteractionEvents, though I doubt this will do what you are looking for. I really think managing state in your application that ensures that you will do the right thing, not moving the piece once the end state has been reached, is the way to go.
From Apple's Event Handling Guide for iOS:
Turning off delivery of touch events for a period. An application can
call the UIApplication method beginIgnoringInteractionEvents and later
call the endIgnoringInteractionEvents method. The first method stops
the application from receiving touch events entirely; the second
method is called to resume the receipt of such events. You sometimes
want to turn off event delivery while your code is performing
animations.
I found this question after wanting to do a very similar action within my own application. I wanted the touchesEnded: method to be run immediately after clicking on the moving action (like a touchDown action method), but did not know how to achieve this. In the end this is what I did and it WORKED! :
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
...
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self touchesEnded:touches withEvent:event];
});
}
This worked perfectly!
Related
In a UIView I have a nav button with an IBAction & method in the top-level view controller.
In the IBAction code, I flip a boolean so that when execution returns to the UIView, there's some new setup prior to drawRect: repainting the view.
If all this were in the ViewController, I could put the new setup code in something like ViewDidAppear so it executes each time the button is pressed. However, there's no such method at the UIView level. There is initWithCoder, but this only seems to be executed once (when the storyboard/nib loads).
So my question is - either, is there a way to call the initiWithCoder method explicitly from my IBAction at the VC level (I've tried [self initWithCoder:nil] but the breakpoint at the UIView level doesn't trigger) or is there a method that runs when execution returns to the UIView level, a la ViewDidAppear?
Thanks
Image of goal:
Unless you really know what you're doing (I mean really know), don't call -initWithCoder: yourself. You're meant to implement it just as you implement -drawRect: and let the system call it. If you ever find yourself calling something like this directly and you can't explain the deep technical reasons why there's no other way, then it's the wrong approach. Read and follow the documentation (not just the method's doc) to make sure you understand whatever method you're using. It'll tell you.
That said, what you're wondering is if there's a point in a view's lifecycle where you can "do something" (check a BOOL and perform some work if YES/NO) any time the view "appears". The answer is yes, and -willMoveToSuperview "can" work.
BUT
That's the "wrong" approach, IMO. The BOOL property ('draw a twiddle next time I'm asked to draw) can and probably should live in the UIView, but its state should be set in its controller since this is specific to your app. Views are supposed to be (highly) reusable; controllers are supposed to implement your app's specific logic and drive the views according to the model state and user (or system) actions.
So: when you want to enable the "draw a twiddle" operation, your view controller should set the view instance's drawTwiddle flag then probably flag the view for drawing. Your view will then have -drawRect: called at some point you shouldn't try to control and, when it does, it sees that self.drawTwiddle == YES and draws the twiddle along with whatever other drawing it does.
At that point, you might be tempted to have the view set its own drawTwiddle flag to NO since the behavior is intended to fire once. Don't do this. BEWARE: Other user actions or system events may call -drawRect: at any time so the twiddle may not actually be seen by the user (it may appear and disappear faster than is visible). 'So', the right thing to do is to make the controller (via some direct action, system event, or timer) responsible for setting and unsetting the drawTwiddle flag, then flagging the view for redisplay.
Adding
It's also unusual to put an IBOutlet or an IBAction in a UIView. Most of the time, unless you're creating some compound control whose parts aren't intended to be accessed and managed individually, your architecture is clearer (and more closely follows the spirit of the MVC design pattern) by letting the controller manage/own the outlets and actions.
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
How to know when the device is charging?
I am kind of a noob at Xcode so sorry if I make I fool out of my self. I am trying to make an app and one of the functions is to check if the iphone/ipod/ipad is charging or not. If not charging I want it to play a sound perhaps and flash LEDs. Also if it is charging, I want to make it display text such as "Device Charging :) " . By the way I am using the method with the FlipsideViewController, but these features will be in the MainView. I have looked at various examples and have seen the one below as well as many test applications, but I don't know how to use it. Thank you in advance!!
Code:
[[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
if ([[UIDevice currentDevice] batteryState] != UIDeviceBatteryStateUnplugged) {
//Device is connected (charging or fully charged)
}
It sounds like you might want to start with some tutorials on writing iPhone apps because the code above looks ok (though I haven't tried this specifically). If you don't understand how to do some of the things I describe below (or some of the terms are unfamiliar), I'd recommend getting a good book or checking out some tutorials:
Book: iOS 6 SDK Development (Amazon)
Tutorials: Ray Wenderlich has many good tutorials on his site
Apple Documentation is quite good
There's no point in just writing the code for you because you presumably want to learn how to program and you won't learn much if someone just does it for you.
Here are some pointers on how to think about this, though the behavior you desire isn't completely obvious from what you've said.
If I understand you you want two different actions to happen when the main view is displayed.
To start with, why not get it to set the text string in a field to match the state of charging / not charging?
So you'd add a UILabel to the main view .xib file in Xcode and add an outlet to it in the view controller so that you can set it's text at runtime.
Then you'll want to call the first line above somewhere once to start monitoring (parameter is: YES) and again with NO to stop monitoring once the view goes away. If you look at the documentation for UIViewController at developer.apple.com near the top you'll see a list of methods that UIViewController implements. In this case you probably want to override (create your own version of) viewWillAppear and viewWillDisappear and put the setBatteryMonitoringEnabled call with YES and NO into these two methods respectively.
So that takes care of enabling / disabling battery status monitoring. Now you need to test the state and take action (this is call to batteryState in your code above).
One question about design which isn't obvious from your description is if you want to do this once when the view appears or if you want it to continually monitor the state.
If you want to do it once, you could put the call to the above in viewDidAppear, say, and then use the outlet to the UILabel to set the message in the label to "Charging" or "Not Charging" based on the result from the batteryState method.
If you want something that watches for changes in the state of charging then you need to subscribe to the notification and put your code to change the UILabel (or whatever you finally do in response to a change) in the handler for that notification.
You figure this out by reading the documentation for UIDevice at developer.apple.com or in Xcode's Organizer window's Documentation section. If you read the documentation for the UIDevice method batteryMonitoringEnabled you can see that the "See Also" section includes two entries for the notifications you can subscribe to to find out when the Level or the State changes: UIDeviceBatteryLevelDidChangeNotification and UIDeviceBatteryStateDidChangeNotification. To learn how to subscribe to notifications you'll want to look at NSNotificationCenter documentation.
Then once you get this working you can add your fancier alerts (be careful about putting them up repeatedly!) and sounds (though playing a sound when the battery isn't charging will use battery which is somewhat questionable.. just make sure not to do it repeatedly perhaps).
good luck!
Add this to the appDelegate.m
- (void)applicationDidBecomeActive:(UIApplication *)application{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(batteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil];
[[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
}
- (void)applicationWillResignActive:(UIApplication *)application {
[[UIDevice currentDevice] setBatteryMonitoringEnabled:NO];
}
- (void)batteryStateDidChange(NSNotification*)notif {
// check the status here.
// See if it is charging, or not and respond to the change.
}
(edited to make code more explicit)
I have a cocoa application that has a dozen scrollViews. I love the elasticity, especially in some cases where I'd actually put some kind of "Easter egg" (kinda like the apple logo in the books app. you scroll down, you see an apple logo.)
My problem is, that I need to limit the amount of exposed content beyond the actual content area. When I scroll with the magic mouse, especially, the elasticity causes the whole scroll content to disappear! Until you release the scroll, it moves back in.
Now, I would like to limit the elasticity to a specific margin. how?
NSScrollView manages a view which has a "canvas" bigger than what is/can be display at any one time. So if you want a different behaviour:
Check (void)setHorizontalScrollElasticity: but that doesn't quite do what you want. (you want to allow a fixed amount of elasticity)
Subclass NSScrollView to implement the behaviour you want.
Create your own class from scratch (well... Anything inheriting from NSResponder since you want to handle events).
For example, I once wrote a world-map program but needed the map to loop forever on the horizontal axis. I just manually managed the scrolling with a subclassed NSView. (don't have access to code currently)
Something to ponder about: I understand your reasons but just wanted to mention it. The behaviour should be expected by the user. If it looks like a button, it should act like out. Currently, scrollviews have the elasticity so that when they scroll via momentum (user is no longer touching), it doesn't stop suddenly once it reaches the end... which would be jarring for users.
Example
If subclassing NSScrollview, I would try overriding - (void)scrollWheel:(NSEvent *) and detect what are the bounds of the contentView and cap it at a certain value. Something around the lines of:
- (void)scrollWheel:(NSEvent *)event
{
[super scrollWheel:event];
if (self.contentView.bounds.origin.y > SomeConstant)
/* cap the value */
}
How could I move a subview to the front of the stack when the LevelMeter detects a certain amount of decibels? I want LevelMetering to be active in real time without a record play function.
Ultimately, I'm trying to animate a mouth to open and close based on sound.
To make a view topmost in the view order, you just have to send a -bringSubviewToFront: to the view that contains it. From a view controller, you might do something like:
[self.view bringSubviewToFront:levelMeterView];
If that doesn't answer your question, perhaps you can expand a bit on what you're asking?
Newbie Warning
I have a simple but vexing problem trying to disable an NSButton. Here is sample code to illustrate the problem:
- (IBAction)taskTriggeredByNSButtonPress:(id)sender {
[ibOutletToNSButton setEnabled:NO];
//A task is performed here that takes some time, during which time
//the button should not respond to presses.
//Once the task is completed, the button should become responsive again.
[ibOutletToNSButton setEnabled:YES];
}
This is what I observe. I press the button. The button becomes disabled (judging by its faded appearance), and the task begins executing. While the button is disabled and the task is executing, I press the button a second time. Nothing happens immediately, but once the task is completed, the taskTriggeredByNSButtonPress: method is called a second time, suggesting that the second button press was placed on hold and then activated once the button became re-enabled.
I've tried all kinds of hacks to prevent the second button press from being recognized, including introducing a time delay after the [ibOutletToNSButton setEnabled:NO]; statement, making the button hidden rather than disabled, covering the button with a custom view during the time it should be disabled, binding the button's enabled status to a property, and other things I'm too embarrassed to mention.
Please help me understand why I can't get this simple task of disabling the button to work.
This method seems to be directly linked to the button. You should perform the long action on another thread, or the main runloop won't be available until the method returns. The main runloop doesn't respond to events while it's not available.
First, create a method:
- (void)someLongTask: (id)sender {
// Do some long tasks…
// Now re-enable the button on the main thread (as required by Cocoa)
[sender performSelectorOnMainThread: #selector(setEnabled:) withObject: YES waitUntilDone: NO];
}
Then, perform that method in a separate thread when the button's clicked:
- (IBAction)buttonPushed: (id)sender {
[sender setEnabled: NO];
[self performSelectorInBackground: #selector(someLongTask) withObject: nil];
}
You may replace self in the example above with the object where -someLongTask resides.
By multi-threading, you leave the main runloop alone and stable. Maybe, your problem will be solved. Otherwise, you solved a problem with responsiveness.
(By the way, if the method is only called by the button, the sender argument is set to the button. That way, you don't need to use an outlet in the method. But that's just a hint.)
You should not perform tasks that require a lot of processing time on the main event loop. This is what you are doing, and the app's entire UI will block while your code executes. Blocking the main thread is the cause of the Spinning Pizza of Death™. In other words, don't do it.
What you need to do instead is break out your time-consuming code so that it runs in another thread, that is, concurrently in the background. When the background task completes, it should somehow notify the code running in the main thread that it has finished. Your code in the main thread can then update the UI appropriately.
There are many ways to do this.
You can use the NSThread methods as suggested by Randy Marsh. However, you must be very careful to read the documentation, as you can't just call any old method on a background thread and expect it to work. You must create your own autorelease pool in the thread and dispose of it correctly. You must NOT call any methods that update the UI from a secondary thread. You must be extremely careful that no variables will be accessed or modified by more than one thread at a time. Threading is a complex business.
The -performSelectorInBackground:withObject: method of NSObject is essentially a simple way of using NSThread and has the same provisos.
You can use the NSOperation and NSOperationQueue methods, which are especially good if you can break down your task's work into small chunks that can be executed simultaneously.
The simplest way of handling this is GCD (Grand Central Dispatch), which allows you to use inline blocks to write background processes:
- (IBAction)taskTriggeredByNSButtonPress:(id)sender
{
[ibOutletToNSButton setEnabled:NO];
//get a reference to the global GCD thread queue
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
//get a reference to the main thread queue
dispatch_queue_t main = dispatch_get_main_queue();
//perform long-running operation
dispatch_async(queue,^{
NSLog(#"Doing something");
sleep(15);
//update the UI on the main thread
dispatch_async(main,^{
[ibOutletToNSButton setEnabled:YES];
});
});
}
GCD is very lightweight and efficient and I highly recommend you use it if possible.
There is a lot more information and detail in Apple's Concurrency Programming Guide, which I recommend you read, even though some of the detail may be beyond you at this stage.