im working with iphone sdk and i got to show a label when the user clik a button but after some time the label deseapear, can i do that?
Use NSObject's performSelector: withObject: afterDelay: for that - it will setup NSTimer that performs selector for you. In button click handler:
...
myLabel.hidden = NO;
[self performSelector:#selector(hideView:) withObject:myLabel afterDelay:3];
...
- (void) hideView:(UIView*)inView{
// You can also add animation here
view.hidden = YES;
}
Note, that it is not guaranteed that hideView: will get called exactly after 3 sec.
Related
I'm using the new adaptive "Present As Popover" capability of iOS 8. I wired up a simple segue in the StoryBoard to do the presentation. It works great on an iPhone 6 Plus as it presents the view as a popover and on an iPhone 4s it shows as a full screen view (sheet style).
The problem is when shown as a full screen view, I need to add a "Done" button to the view so dismissViewControllerAnimated can be called. And I don't want to show the "done" button when it's shown as a popover.
I tried looking at the properties of both presentationController and popoverPresentationController, and I can find nothing that tells me if it is actually being shown as a popover.
NSLog( #"View loaded %lx", (long)self.presentationController.adaptivePresentationStyle ); // UIModalPresentationFullScreen
NSLog( #"View loaded %lx", (long)self.presentationController.presentationStyle ); // UIModalPresentationPopover
NSLog( #"View loaded %lx", (long)self.popoverPresentationController.adaptivePresentationStyle ); // UIModalPresentationFullScreen
NSLog( #"View loaded %lx", (long)self.popoverPresentationController.presentationStyle ); // UIModalPresentationPopover
adaptivePresentationStyle always returns UIModalPresentationFullScreen and presentationStyle always returns UIModalPresentationPopover
When looking at the UITraitCollection I did find a trait called "_UITraitNameInteractionModel" which was only set to 1 when it was actually displayed as a Popover. However, Apple doesn't provide direct access to that trait through the traitCollection of popoverPresentationController.
The best way (least smelly) I've found to do this is to use the UIPopoverPresentationControllerDelegate.
• Ensure the presented view controller is set as the UIPopoverPresentationControllerDelegate on the UIPopoverPresentationController being used to manage the presentation. I'm using a Storyboard so set this in prepareForSegue:
segue.destinationViewController.popoverPresentationController.delegate = presentedVC;
• Create a property in the presented view controller to keep track of this state:
#property (nonatomic, assign) BOOL amDisplayedInAPopover;
• And add the following delegate method (or add to your existing delegate method):
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// This method is only called if we are presented in a popover
self.amDisplayedInAPopover = YES;
}
• And then finally in viewWillAppear: - viewDidLoad: is too early, the delegate prepare method is called between viewDidLoad: and viewWillAppear:
if (self.amDisplayedInAPopover) {
// Hide the offending buttons in whatever manner you do so
self.navigationItem.leftBarButtonItem = nil;
}
Edit: Simpler method!
Just set the delegate (making sure your presentedVC adopts the UIPopoverPresentationControllerDelegate):
segue.destinationViewController.popoverPresentationController.delegate = presentedVC;
And supply the method:
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// This method is only called if we are presented in a popover
// Hide the offending buttons in whatever manner you do so
self.navigationItem.leftBarButtonItem = nil;
}
I check to see if the popoverPresentationController's arrowDirection is set after the view is laid out. For my purposes, this works well enough and covers the case of popovers on smaller screened devices.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (popoverPresentationController?.arrowDirection != UIPopoverArrowDirection.Unknown) {
// This view controller is running in a popover
NSLog("I'm running in a Popover")
}
}
How about
if (self.modalPresentationStyle == UIModalPresentationPopover)
It's working for me
The official way to implement this is first remove the Done button from your view controller and second, when adapting to compact embed your view controller in a navigation controller, adding the done button as a navigation item:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.FullScreen
}
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
return navigationController
}
func dismiss() {
self.dismissViewControllerAnimated(true, completion: nil)
}
Full Tutorial
I tested all solutions presented in this post. Sorry, none works correctly in all cases. For example in iPad split view presentation style can change while dragging split view line, so we need specific notification for that.
After few hours of researches i found solution in apple sample (swift):
https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Introduction/Intro.html#//apple_ref/doc/uid/TP40014636
Here is the same solution in obj-c.
First in prepareForSegue function set the popoverPresentationController delegate. It can be also set in MyViewController "init", but not in "viewDidLoad" (because first willPresentWithAdaptiveStyle is called before viewDidLoad).
MyViewController *controller = [segue destinationViewController];
controller.popoverPresentationController.delegate = (MyViewController *)controller;
Now MyViewController object will receive this notification every time iOS changes presentation style, including first presenting. Here is example implementation which shows/hides "Close" button in navigationController:
- (void)presentationController:(UIPresentationController *)presentationController
willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style
transitionCoordinator:(nullable id<UIViewControllerTransitionCoordinator>)transitionCoordinator {
if (style == UIModalPresentationNone) {
// style set in storyboard not changed (popover), hide close button
self.topViewController.navigationItem.leftBarButtonItem = nil;
} else {
// style changed by iOS (to fullscreen or page sheet), show close button
UIBarButtonItem *closeButton =
[[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeAction)];
self.topViewController.navigationItem.leftBarButtonItem = closeButton;
}
}
- (void)closeAction {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
The UIPresentationController which manages your view controller is presenting it by setting the modalPresentationStyle to UIModalPresentationPopover.
As per UIViewController reference:
presentingViewController
The view controller that presented this view
controller. (read-only)
modalPresentationStyle
UIModalPresentationPopover: In a horizontally regular environment, a presentation style where the content is displayed in a popover view. The background content is dimmed and taps
outside the popover cause the popover to be dismissed. If you do not
want taps to dismiss the popover, you can assign one or more views to
the passthroughViews property of the associated
UIPopoverPresentationController object, which you can get from the
popoverPresentationController property.
We can therefore determine whether your view controller is inside a popover or presented modally by checking the horizontalSizeClass as follows (I assumed your button is a UIBarButtonItem)
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.presentingViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
self.navigationItem.leftBarButtonItem = nil; // remove the button
}
The safest place to check this is in viewWillAppear: as otherwise the presentingViewController may be nil.
Solution that works with multitasking
Assign the presenting controller as the popover's delegate
...
controller.popoverPresentationController.delegate = controller;
[self presentViewController:controller animated:YES completion:nil];
Then, in the controller, implement the delegate methods:
- (void)presentationController:(UIPresentationController *)presentationController willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style transitionCoordinator:(id<UIViewControllerTransitionCoordinator>)transitionCoordinator
{
if (style != UIModalPresentationNone)
{
// Exited popover mode
self.navigationItem.leftBarButtonItem = button;
}
}
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// Entered popover mode
self.navigationItem.leftBarButtonItem = nil;
}
My tricky solution, works perfectly.
In the PopoverViewController's viewDidLoad.
if (self.view.superview!.bounds != UIScreen.main.bounds) {
print("This is a popover!")
}
The idea is simple, A Popover's view size is never equal to the device screen size unless it's not a Popover.
After switching to iOS8, I'm getting weird behavior when I move views during a keyboard transition. Can anyone explain what's going on?
Here's a minimal example to demonstrate the problem. I have a simple view with a UITextField and a UIButton. The function nudgeUp moves the text field and the button up by 10 points. It is triggered either by the buttonPressed callback, or the keyboardWillShow callback.
When I tap the button, the code works as expected: buttonPressed calls nudgeUp and the button and text field jump up by 10 points.
When I tap the text field, keyboardWillShow calls nudgeUp, but the behaviour is very different. The button and text field immediately jump down by 10 points, and then slide back up to their original position as the keyboard shows itself.
Why is this happening? How can I regain control of animations during keyboard presentation in iOS8?
#import "ViewController.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
// Called when the keyboard appears.
[self nudgeUp];
}
- (IBAction)buttonPressed:(id)sender {
[self nudgeUp];
}
- (void)nudgeUp
{
CGRect newTextFieldFrame = self.textField.frame;
newTextFieldFrame.origin.y -= 10;
self.textField.frame = newTextFieldFrame;
CGRect newButtonFrame = self.button.frame;
newButtonFrame.origin.y -= 10;
self.button.frame = newButtonFrame;
}
#end
It's AutoLayout. Something changed in iOS8 and you can't just change frame or center points anymore if you have AutoLayout enabled. You have to create an outlet(s) of your constraint (vertical space) and update it accordingly instead of changing frame position. Constraints are like any other ui control and can have an outlet. Constraint change can be animated.
Example:
[UIView animateWithDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue] delay:0 options:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue] animations:^{
self.bottomSpaceConstraint.constant = adjustmentedValue;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
You should use UIKeyboardDidShowNotification (you're using will version) and everything will work as you expect:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
}
- (void)keyboardDidShow:(NSNotification *)notification
{
// Called when the keyboard finished showing up
[self nudgeUp];
}
The explanation is that with UIKeyboardWillShowNotification you are changing the frames too early. After your changes the system will relayout everything to accomodate the keyboard and your changes won't have any effect.
Also, I recommend you to switch to autolayout and forget about frames.
Try using the UIKeyboardWillShowNotification userInfo to give you the frame of the keyboard. Then move the onscreen elements based on that.
I have been working on this for hours, have no idea what went wrong. I want a custom cursor for a button which is a subview of NSTextView, I add a tracking area and send the cursorUpdate message when mouse entered button.
The cursorUpdate method is indeed called every time the mouse entered the tracking area. But the cursor stays the IBeamCursor.
Any ideas?
Reference of the Apple Docs: managing cursor-update event
- (void)cursorUpdate:(NSEvent *)event {
[[NSCursor arrowCursor] set];
}
- (void)myAddTrackingArea {
[self myRemoveTrackingArea];
NSTrackingAreaOptions trackingOptions = NSTrackingCursorUpdate | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
_trackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds] options: trackingOptions owner: self userInfo: nil];
[self addTrackingArea: _trackingArea];
}
- (void)myRemoveTrackingArea {
if (_trackingArea)
{
[self removeTrackingArea: _trackingArea];
_trackingArea = nil;
}
}
I ran into the same problem.
The issue is, that NSTextView updates its cursor every time it receives a mouseMoved: event. The event is triggered by a self updating NSTrackingArea of the NSTextView, which always tracks the visible part of the NSTextView inside the NSScrollView. So there are maybe 2 solutions I can think of.
Override updateTrackingAreas remove the tracking area that is provided by Cocoa and make sure you always create a new one instead that excludes the button. (I would not do this!)
Override mouseMoved: and make sure it doesn't call super when the cursor is over the button.
- (void)mouseMoved:(NSEvent *)theEvent {
NSPoint windowPt = [theEvent locationInWindow];
NSPoint superViewPt = [[self superview]
convertPoint: windowPt fromView: nil];
if ([self hitTest: superViewPt] == self) {
[super mouseMoved:theEvent];
}
}
I had the same issue but using a simple NSView subclass that was a child of the window's contentView and did not reside within an NScrollView.
The documentation for the cursorUpdate flag of NSTrackingArea makes it sound like you only need to handle the mouse entering the tracking area rect. However, I had to manually check the mouse location as the cursorUpdate(event:) method is called both when the mouse enters the tracking area's rect and when it leaves the tracking rect. So if the cursorUpdate(event:) implementation only sets the cursor without checking whether it lies within the tracking area rect, it is set both when it enters and leaves the rect.
The documentation for cursorUpdate(event:) states:
Override this method to set the cursor image. The default
implementation uses cursor rectangles, if cursor rectangles are
currently valid. If they are not, it calls super to send the message
up the responder chain.
If the responder implements this method, but decides not to handle a
particular event, it should invoke the superclass implementation of
this method.
override func cursorUpdate(with event: NSEvent) {
// Convert mouse location to the view coordinates
let mouseLocation = convert(event.locationInWindow, from: nil)
// Check if the mouse location lies within the rect being tracked
if trackingRect.contains(mouseLocation) {
// Set the custom cursor
NSCursor.openHand.set()
} else {
// Reset the cursor
super.cursorUpdate(with: event)
}
}
I just ran across this through a Google search, so I thought I'd post my solution.
Subclass the NSTextView/NSTextField.
Follow the steps in the docs to create an NSTrackingArea. Should look something like the following. Put this code in the subclass's init method (also add the updateTrackingAreas method):
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:(NSTrackingMouseMoved | NSTrackingActiveInKeyWindow) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
self.trackingArea = trackingArea;
Now you need to add the mouseMoved: method to the subclass:
- (void)mouseMoved:(NSEvent *)theEvent {
NSPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
if (NSPointInRect(point, self.popUpButton.frame)) {
[[NSCursor arrowCursor] set];
} else {
[[NSCursor IBeamCursor] set];
}
}
Note: the self.popUpButton is the button that is a subview of the NSTextView/NSTextField.
That's it! Not too hard it ends up--just had to used mouseMoved: instead of cursorUpdate:. Took me a few hours to figure this out, hopefully someone can use it.
I'm trying to close a modal alert sheet programmatically when called like this:
[alert beginSheetModalForWindow:contacts modalDelegate:self didEndSelector:#selector(myAlertEnded:code:context:) contextInfo:NULL];
I had the same issue and I solved this way.
To launch the sheet:
[myAlertSheet beginSheetModalForWindow:self.view.window modalDelegate:self didEndSelector:#selector(showAlertDidEnd: returnCode: contextInfo:) contextInfo:nil];
To programmatically close the modal sheet:
[NSApp endSheet:[myAlertSheet window]];
myAlertSheet is an NSAlert instance variable to keep track of the modal sheet on-screen. The endSheet message then calls the selector:
- (void)showAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
{...}
I hope the above is useful
You'd have a method like this in your controller (the modalDelegate):
- (IBAction) cancelClicked: (id) sender {
// Cancel the sheet and close.
[NSApp endSheet: [self window]];
}
... which would be wired to a Cancel button in the modal sheet (or to an OK button for that matter but that would probably invoke some spin-off processing).
You also need to implement this didEndSelector to actually remove the sheet:
- (void) didEndSheet: (id) modalSheet returnCode: (NSInteger) returnCode contextInfo: (void*) contextInfo {
// Remove the sheet.
[modalSheet orderOut: nil];
}
If I remember correctly I scooped this from an example in the Apple docs.
In NSWindow you'll find a method called attachedSheet. It yields a reference to a sheet attached to this window, if any. The sheet itself is also simply a NSWindow. Therefore, you might want to try this:
NSWindow *window = [NSApp mainWindow];
[[window attachedSheet] close];
The way I found it we need to both endSheet(alert.window) and alert.window.close().
let hostWindow = contacts
// Dismiss the sheet. Yet its window will stay on screen.
// .endSheet(:) will yield NSApplication.ModalResponse.stop
hostWindow.endSheet(alert.window)
// Close the alert window.
alert.window.close()
OR
let hostWindow = contacts
alert.beginSheetModal(for: hostWindow) { response in
switch response {
case .stop: alert.window.close()
default: fatalError("TODO")
}
}
// Later... Dismiss the sheet.
// .endSheet(:) will yield NSApplication.ModalResponse.stop
hostWindow.endSheet(alert.window)
Using NSAlert.runModal() we need instead:
let response = alert.runModal()
switch response {
case .stop: break
default: fatalError("TODO")
}
// Later... Dismiss the NSApp.modalWindow
// stopModal() will yield NSApplication.ModalResponse.stop
NSApp.stopModal()
I have a stopwatch that runs fine on my app however when I click on the back button to go to another view it stops. I want the stopwatch to keep running even when the user is viewing another page.
How do I accomplish this?
Ok added the code
code:
.h
UILabel *stopWatchLabel;
NSTimer *stopWatchTimer; // Store the timer that fires after a certain time
NSDate *startDate; // Stores the date of the click on the start button
#property (nonatomic, retain) IBOutlet UILabel *stopWatchLabel;
- (IBAction)onStartPressed:(id)sender;
- (IBAction)onStopPressed:(id)sender;
.m
- (void)viewDidUnload
{
[self setStopWatchLabel:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss.SSS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
}
- (IBAction)onStartPressed:(id)sender {
startDate = [NSDate date];
// Create the stop watch timer that fires every 10 ms
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
- (IBAction)onStopPressed:(id)sender {
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self updateTimer];
}
Well it stops because you're releasing the view that it lives in when you pop it off the stack.
have you tried making a stopwatch class that handles the timer independent of the view?
The stopwatch stops because you're releasing the view that controls it.
You need to make sure that the NSTimer is not linked to any view.
Perhaps you can use subviews or Modal View Controllers (I think that may work) in order to not have the stopwatch released when the view is.
You could also try sending the stopwatch information to the next view through variables, and then having the other view take over the stopwatch from there.
There are most definitely other methods as well.
Either way, you need to make sure that the NSTimer is not released.
Exactly, if you look at your "ViewDidUnload" you're setting it to nil when the view gets released which it does when you go to another view.
Something you can try is to have a class that's running the timer and sending it over to the view that way it's still running even though the view that displays it is not in memory.
MVC style!
Yeah, what everyone else says.
On the ViewDidUnload it is releasing it. Meaning it is basically ending the operation as soon as that ViewController is exited.
I Dont know if the problem is already solved or not but still.
Create the Object of NSTimer in App Delegate Class and then instantiate the timer of in .m file of app delegate in this way your NSTimer will stay in memory. and when you want to stop or pause the timer you can just invalidate that object from any view Controller.