I have eight UIImageViews that I want to fade if the UITapGestureRecognizer that is associated with it is activated. I have the all recognizers hooked up to this IBAction:
- (IBAction)disableDie:(id)sender {
NSLog(#"%#", sender);
NSLog(#"%ld",[(UIGestureRecognizer *)sender view].tag);
}
I thought I could do it with a loop like this:
- (IBAction)disableDie:(id)sender {
for (UIImageView *numberImage in self.diceOutletArray) {
if (numberImage == sender) {
numberImage.alpha = 0.65;
}
}
NSLog(#"%#", sender);
NSLog(#"%ld",[(UIGestureRecognizer *)sender view].tag);
}
But nothing happens to the UIImageView that was pressed, but the message's are printed. I have used the diceOutletArray in other loops and it works.
The sender is a UITapGestureRecognizer, not a UIImageView, and
therefore numberImage == sender will never be true.
Try this instead:
- (IBAction)disableDie:(UIGestureRecognizer *)sender {
for (UIImageView *numberImage in self.diceOutletArray) {
if (numberImage == sender.view) {
numberImage.alpha = 0.65;
break;
}
}
}
You don't actually need the loop at all though, this would work fine as well:
- (IBAction)disableDie:(UIGestureRecognizer *)sender {
sender.view.alpha = 0.65;
}
The gesture recognizer is the sender, not the view. You should see that in the printout of sender. You need to get the recognizer's view (assuming that it's attached directly to its image view).
Once you have that, you don't really need to go and find another pointer to the view: you already have it. It's just called sender.view instead of mumbleMumbleImageView.
Just send setAlpha: to that pointer.
I'm trying to add a gesture recognizer to specific tabBar item's subview. I can successfully add one to the tabBar itself, but not a specific index.
I was able to trick it into reacting based on a selectedIndex by implementing this in my AppDelegate:
[self.tabBar.view addGestureRecognizer:longPressNotificationsGR];
-(void)showFilterForNotifications:(UILongPressGestureRecognizer *)gesture {
if (self.tabBar.selectedIndex == 4) {
if (gesture.state == UIGestureRecognizerStateBegan) {
NSLog(#"Began");
} else if (gesture.state == UIGestureRecognizerStateEnded) {
NSLog(#"Ended");
}
}
}
Is there a better way? And yes I know this isn't what Apple had in mind but this is what I need for my particular project. I just feel like this isn't the best workaround, if there even is one, and i'm not sure how this will behave performance wide since its in the AppDelegate. But ultimately, I don't want to do it this way, I want to add it to the objectAtIndex:n so users can't just press and hold anywhere on the tabBar, even if 4 is the currently selected index. Right now users can tap & hold on index 1 icon, if 4 is selected, and the gesture methods get called. I want it to happen only if user is tapping & holding on objectAtIndex:n
You can get the UIView of a tab at a specific index with the following category method on UITabBar:
- (UIView*)tabAtIndex:(NSUInteger)index
{
BOOL validIndex = index < self.items.count;
NSAssert(validIndex, #"Tab index out of range");
if (!validIndex) {
return nil;
}
NSMutableArray* tabBarItems = [NSMutableArray arrayWithCapacity:[self.items count]];
for (UIView* view in self.subviews) {
if ([view isKindOfClass:NSClassFromString(#"UITabBarButton")] && [view respondsToSelector:#selector(frame)]) {
// check for the selector -frame to prevent crashes in the very unlikely case that in the future
// objects that don't implement -frame can be subViews of an UIView
[tabBarItems addObject:view];
}
}
if ([tabBarItems count] == 0) {
// no tabBarItems means either no UITabBarButtons were in the subView, or none responded to -frame
// return CGRectZero to indicate that we couldn't figure out the frame
return nil;
}
// sort by origin.x of the frame because the items are not necessarily in the correct order
[tabBarItems sortUsingComparator:^NSComparisonResult(UIView* view1, UIView* view2) {
if (view1.frame.origin.x < view2.frame.origin.x) {
return NSOrderedAscending;
}
if (view1.frame.origin.x > view2.frame.origin.x) {
return NSOrderedDescending;
}
NSLog(#"%# and %# share the same origin.x. This should never happen and indicates a substantial change in the framework that renders this method useless.", view1, view2);
return NSOrderedSame;
}];
if (index < [tabBarItems count]) {
// viewController is in a regular tab
return tabBarItems[index];
}
else {
// our target viewController is inside the "more" tab
return [tabBarItems lastObject];
}
return nil;
}
Then, you just need to add your gesture recognizer:
[[self.tabBarController.tabBar tabAtIndex:4] addGestureRecognizer:myGestureRecognizer];
How do I test if a subview has already been added to a parent view? If it hasn't been added, I want to add it. Otherwise, I want to remove it.
You can use the UIView method isDescendantOfView:
if mySubview.isDescendant(of: someParentView) {
mySubview.removeFromSuperview()
} else {
someParentView.addSubview(mySubview)
}
You may also need to surround everything with if mySubview != nil depending on your implementation.
This is a much cleaner way to do it:
if myView != nil { // Make sure the view exists
if self.view.subviews.contains(myView) {
self.myView.removeFromSuperview() // Remove it
} else {
// Do Nothing
}
}
}
for view in self.view.subviews {
if let subView = view as? YourNameView {
subView.removeFromSuperview()
break
}
}
Here we used two different views. Parent view is the view in which we are searching for descendant view and check wether added to parent view or not.
if parentView.subviews.contains(descendantView) {
// descendant view added to the parent view.
}else{
// descendant view not added to the parent view.
}
I have a UIView, let's say MyView which contains a UILabel, UISegmentControl and a UIButton.
So
MyView.Hidden = false;
and
MyView.Tag = 1000;
to make sure it doesn't touch it.
now when I click outside that view I want it to hide, to that end I have some code which adds tap recognisers, ignores MyView (I hoped) and hides the view when you click outside. It removes the tab recognisers until MyView shows up again. It works, however when I click on the subviews of MyView (but NOT MyView itself) it calls ViewTap as well and thus disappears it as well. I cannot find why this happens...
class ViewTap : OutsideEditTap
{
public ViewTap(UIView MainView) {
this.AddTarget(() => {
MyView.Hidden = true;
RemoveTapGesture(this.View);
});
CancelsTouchesInView = false;
}
}
class OutsideEditTap : UITapGestureRecognizer
{
}
public static void RemoveTapGesture(UIView view) {
if (view.GestureRecognizers != null) for (int i = 0; i < view.GestureRecognizers.Length; i++) {
if (view.GestureRecognizers[i].IsSubClassOf(typeof(OutsideEditTap))) {
view.RemoveGestureRecognizer (view.GestureRecognizers [i]);
}
}
foreach (UIView subView in view.Subviews) {
RemoveTapGesture (subView);
}
}
public static void AddTapGesture(UIView view, UIView MainView) {
OutsideEditTap tap = null;
if (view.Tag != 1000) { // skip MyView
tap = new ViewTap (MainView);
view.AddGestureRecognizer (tap);
foreach (UIView subView in view.Subviews) {
AddTapGesture (subView, MainView);
}
}
}
I am sure this is probably not the best method (I don't think the whole idea was not the best implementation to begin with, but no-one provided any alternatives unfortunately); the solution I used to solve this was to add to the AddTarget Action:
var p = this.LocationOfTouch(0, this.View);
if (MyView.Frame.Contains(p)) { return; }
which works well. I would still like to hear a better way of doing this.
I have a tab bar application, with many views. Is there a way to know if a particular UIViewController is currently visible from within the UIViewController? (looking for a property)
The view's window property is non-nil if a view is currently visible, so check the main view in the view controller:
Invoking the view method causes the view to load (if it is not loaded) which is unnecessary and may be undesirable. It would be better to check first to see if it is already loaded. I've added the call to isViewLoaded to avoid this problem.
if (viewController.isViewLoaded && viewController.view.window) {
// viewController is visible
}
Since iOS9 it has became easier:
if viewController.viewIfLoaded?.window != nil {
// viewController is visible
}
Or if you have a UINavigationController managing the view controllers, you could check its visibleViewController property instead.
Here's #progrmr's solution as a UIViewController category:
// UIViewController+Additions.h
#interface UIViewController (Additions)
- (BOOL)isVisible;
#end
// UIViewController+Additions.m
#import "UIViewController+Additions.h"
#implementation UIViewController (Additions)
- (BOOL)isVisible {
return [self isViewLoaded] && self.view.window;
}
#end
There are a couple of issues with the above solutions. If you are using, for example, a UISplitViewController, the master view will always return true for
if(viewController.isViewLoaded && viewController.view.window) {
//Always true for master view in split view controller
}
Instead, take this simple approach which seems to work well in most, if not all cases:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
//We are now invisible
self.visible = false;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//We are now visible
self.visible = true;
}
For those of you looking for a Swift 2.2 version of the answer:
if self.isViewLoaded() && (self.view.window != nil) {
// viewController is visible
}
and Swift 3:
if self.isViewLoaded && (self.view.window != nil) {
// viewController is visible
}
For over-full-screen or over-context modal presentation, "is visible" could mean it is on top of the view controller stack or just visible but covered by another view controller.
To check if the view controller "is the top view controller" is quite different from "is visible", you should check the view controller's navigation controller's view controller stack.
I wrote a piece of code to solve this problem:
extension UIViewController {
public var isVisible: Bool {
if isViewLoaded {
return view.window != nil
}
return false
}
public var isTopViewController: Bool {
if self.navigationController != nil {
return self.navigationController?.visibleViewController === self
} else if self.tabBarController != nil {
return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil
} else {
return self.presentedViewController == nil && self.isVisible
}
}
}
You want to use the UITabBarController's selectedViewController property. All view controllers attached to a tab bar controller have a tabBarController property set, so you can, from within any of the view controllers' code:
if([[[self tabBarController] selectedViewController] isEqual:self]){
//we're in the active controller
}else{
//we are not
}
I made a swift extension based on #progrmr's answer.
It allows you to easily check if a UIViewController is on screen like so:
if someViewController.isOnScreen {
// Do stuff here
}
The extension:
//
// UIViewControllerExtension.swift
//
import UIKit
extension UIViewController{
var isOnScreen: Bool{
return self.isViewLoaded() && view.window != nil
}
}
For my purposes, in the context of a container view controller, I've found that
- (BOOL)isVisible {
return (self.isViewLoaded && self.view.window && self.parentViewController != nil);
}
works well.
I use this small extension in Swift 5, which keeps it simple and easy to check for any object that is member of UIView.
extension UIView {
var isVisible: Bool {
guard let _ = self.window else {
return false
}
return true
}
}
Then, I just use it as a simple if statement check...
if myView.isVisible {
// do something
}
I hope it helps! :)
Good point that view is appeared if it's already in window hierarchy stack.
thus we can extend our classes for this functionality.
extension UIViewController {
var isViewAppeared: Bool { viewIfLoaded?.isAppeared == true }
}
extension UIView {
var isAppeared: Bool { window != nil }
}
XCode 6.4, for iOS 8.4, ARC enabled
Obviously lots of ways of doing it. The one that has worked for me is the following...
#property(nonatomic, readonly, getter=isKeyWindow) BOOL keyWindow
This can be used in any view controller in the following way,
[self.view.window isKeyWindow]
If you call this property in -(void)viewDidLoad you get 0, then if you call this after -(void)viewDidAppear:(BOOL)animated you get 1.
Hope this helps someone. Thanks! Cheers.
if you're utilizing a UINavigationController and also want to handle modal views, the following is what i use:
#import <objc/runtime.h>
UIViewController* topMostController = self.navigationController.visibleViewController;
if([[NSString stringWithFormat:#"%s", class_getName([topMostController class])] isEqualToString:#"NAME_OF_CONTROLLER_YOURE_CHECKING_IN"]) {
//is topmost visible view controller
}
The approach that I used for a modal presented view controller was to check the class of the presented controller. If the presented view controller was ViewController2 then I would execute some code.
UIViewController *vc = [self presentedViewController];
if ([vc isKindOfClass:[ViewController2 class]]) {
NSLog(#"this is VC2");
}
I found those function in UIViewController.h.
/*
These four methods can be used in a view controller's appearance callbacks to determine if it is being
presented, dismissed, or added or removed as a child view controller. For example, a view controller can
check if it is disappearing because it was dismissed or popped by asking itself in its viewWillDisappear:
method by checking the expression ([self isBeingDismissed] || [self isMovingFromParentViewController]).
*/
- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);
Maybe the above functions can detect the ViewController is appeared or not.
If you are using a navigation controller and just want to know if you are in the active and topmost controller, then use:
if navigationController?.topViewController == self {
// Do something
}
This answer is based on #mattdipasquale's comment.
If you have a more complicated scenario, see the other answers above.
you can check it by window property
if(viewController.view.window){
// view visible
}else{
// no visible
}
I needed this to check if the view controller is the current viewed controller, I did it via checking if there's any presented view controller or pushed through the navigator, I'm posting it in case anyone needed such a solution:
if presentedViewController != nil || navigationController?.topViewController != self {
//Viewcontroller isn't viewed
}else{
// Now your viewcontroller is being viewed
}
Window:
window.isVisible
viewController.view.window?.isVisible ?? false
View (macOS):
extension NSViewController {
var isOnScreen: Bool {
return ( self.isViewLoaded && view.window != nil )
}
}