how to check if the class is created by user of it is a class provided in pobjective-c API - objective-c

i have a user-defined class of type CircularDynamicUIView. it is in an array the encompass several views like 'buttonviews, uiimageviews,scrollviews and others.
how to programmatically within a loop to detect this user-defined class. for example: using if-statement
how to check if this class is the class created or developed by the user and not the one that already created by objective-c.

As #rmaddy noted in comments, you cannot explicitly differentiate between, for example, your custom CircularDynamicUIView subclass and a default UIView ... but you can evaluate in a specific order.
Example (using your previous question):
for (id uiComponent in uiviews) {
if ([uiComponent isKindOfClass:[CircularDynamicUIView class]]) {
NSLog(#"Yes, it is a CircularDynamicUIView");
} else {
NSLog(#"No, it is NOT a CircularDynamicUIView");
}
// if CircularDynamicUIView is a subclass descendant of UIView
// this will also be true
if ([uiComponent isKindOfClass:[UIView class]]) {
NSLog(#"Yes, it is a UIView");
} else {
NSLog(#"No, it is NOT a UIView");
}
}
Since both if conditions will be true, you would not want to evaluate the second if if the first test was true.
Again, based on your previous question, you could use this approach:
for (id uiComponent in uiviews) {
CircularDynamicUIView *cdView;
UIView *uiView;
UIImageView *uiImageView;
if ([uiComponent isKindOfClass:[CircularDynamicUIView class]]) {
cdView = (CircularDynamicUIView *)uiComponent;
} else if ([uiComponent isKindOfClass:[UIImageView class]]) {
uiImageView = (UIImageView *)uiComponent;
} else if ([uiComponent isKindOfClass:[UIView class]]) {
uiView = (UIView *)uiComponent;
}
if (cdView) {
// do what you want because it's a CircularDynamicUIView
if ([cdView isHidden]) {
// ...
}
// etc ...
}
if (uiImageView) {
// do what you want because it's a UIImageView
if ([uiImageView isHidden]) {
// ...
}
// etc ...
}
if (uiView) {
// do what you want because it's a UIView
if ([uiView isHidden]) {
// ...
}
// etc ...
}
}

Related

How to enable an UISwitch while disabling other two simultaneously

I have 3 UISwitches in my View Controller and each one identifies gender ("Woman, Male, No Gender") that user has to choose.
The behavior I would like to implement is that, when the user taps on a switch to select the gender, the others two have to be disabled simultaneously.
And once the switch is selected, the "Create Profile" button is activated. (screenshots attached)
I'm not able to implement the if condition (or alternatively a switch condition)to do that.
Please help.
This is the code I implemented:
- (IBAction)checkGenderSwitch:(id)sender {
if ([self.femaleGenderSwitch isOn] && (![self.maleGenderSwitch isOn] && ![self.noGenderSwitch isOn])) {
[self enableCreateProfileButton];
} else if ([self.maleGenderSwitch isOn] && (![self.femaleGenderSwitch isOn] && ![self.noGenderSwitch isOn])) {
[self enableCreateProfileButton];
} else if ([self.noGenderSwitch isOn] && (![self.femaleGenderSwitch isOn] && ![self.maleGenderSwitch isOn])) {
[self enableCreateProfileButton];
}else{
[self disableCreateProfileButton];
}
}
Thanks.
Let's reformulate your need:
Every time you change a switch value, you do (in pseudo code):
if (male isOn && female isOff && noGender isOff) { enableButton }
else if (male isOff && female isOn && noGender isOff) { enableButton}
else if (male isOff && female isOff && noGender isOn)) { enableButton }
else { disableButton }
Which could be translated in:
if (onlyMale isOn) { enableButton }
else if (onlyFemale isOn) { enableButton}
else if (onlyNoGender isOn) { enableButton }
else { disableButton }
only... isOn? What about putting them into a NSArray, and count the number of isOn? That should do the trick no? only... isOn is for count = 0 where isOn == true.
Which could be then coded:
NSArray *switches = #[self.femaleGenderSwitch, self.maleGenderSwitch, self.noGenderSwitch];
NSUInteger numberOfSwitchesOn = 0;
for (UISwitch *aSwitch in switches) {
if ([aSwitch isOn]) {
numberOfSwitchesOn += 1;
}
}
if (numberOfSwitchesOn == 1) {
[self enableCreateProfileButton];
} else {
[self disableCreateProfileButton];
}
Now, if you want that when you switchOn a Switch, you switchOff the other two (if needed), which in our case would only be one, it can be done with:
NSArray *switches = #[self.femaleGenderSwitch, self.maleGenderSwitch, self.noGenderSwitch];
BOOL hasASwitchOn = NO;
//We iterate over the switches
for (UISwitch *aSwitch in switches) {
//If the newly set is set to on, we need to disable the other (the one which aren't the same as the sender)
if ((UISwitch *)[sender isOn] && ![sender isEqual:aSwitch]) {
[aSwitch setOn:NO];
hasASwitchOn = YES;
} else if ([aSwitch isOn]) { //Else, we still count the number of isOn
hasASwitchOn = YES;
}
}
if (hasASwitchOn) {
[self enableCreateProfileButton];
} else {
[self disableCreateProfileButton];
}
As has been said in a comment, the disablement requirement is wrong. All three switches need to be enabled at all times. The rule should be simply that only one switch at a time can be on.
The key here is that when you tap a switch, and the IBAction method is called, the sender is that switch.
So just keep a list references to all three switches and if the sender is now on, cycle thru the list and turn off each switch that is not the sender.
(Or use a three part UISegmentedControl which does all this for you automatically; it lets the user choose exactly one of three.)
I would use the tag property of UIView for your three switches, giving each of the three its own, unique tag value: for instance, 0, 1, 2. (You can set that up in Interface Builder, or in code — whichever you prefer.) This makes it trivial to distinguish the three switches.
Then, create one extra property, genders, an array of your three switches:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UISwitch* femaleGenderSwitch;
#property (nonatomic, weak) IBOutlet UISwitch* maleGenderSwitch;
#property (nonatomic, weak) IBOutlet UISwitch* noGenderSwitch;
#property (nonatomic, strong) NSArray* genders;
#end
In -viewDidLoad set that up:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.genders =
#[[self femaleGenderSwitch], [self maleGenderSwitch], [self noGenderSwitch]];
}
Finally, you can simplify your logic in your action method like this:
- (IBAction)checkGenderSwitch:(id)sender
{
if ([sender isOn]) {
for (id aSwitch in [self genders]) {
if ([aSwitch tag] != [sender tag]) {
[aSwitch setOn:NO animated:YES];
}
}
[self enableCreateProfileButton];
}
else {
[self disableCreateProfileButton];
}
}

Bindings does not update NSTextField

I use bindings to NSObjectController within XIB. When I set new content object of NSObjectController the only textfield value which doesn't change is the one that has first responder. Model changes without an issue.
If I don't use custom getter/setter the textfield that has firstResponder (isBeingEdited) changes without an issue.
What's wrong with my KVC, KVO?
My custom getter/setter is below pic.
PS: I don't want to make window a first responder before I change content object to make it work.
static const CGFloat MMsInOneInch = 25.4;
static const CGFloat inchesInOneMM = 0.0393700787402;
- (void)setPaperWidth:(CGFloat)paperWidth
{
[self willChange];
CGFloat newWidth = paperWidth * [self conversionKoeficientToDefaultUnitType];
if (!_isChangingPaperSize) {
if (self.paperSize == PPPaperSizeA4 && fabs(newWidth - widthSizeOfA4) > 0.001) {
[self setPaperSize:PPPaperSizeCustom];
}
if (self.paperSize == PPPaperSizeUSLetter && fabs(newWidth - widthSizeOfUSLetter) > 0.001 ) {
[self setPaperSize:PPPaperSizeCustom];
}
}
[self willChangeValueForKey:#"paperWidth"];
_paperWidth = newWidth;
[self didChangeValueForKey:#"paperWidth"];
[self didChange];
}
- (CGFloat)conversionKoeficientToDefaultUnitType
{
if ([self defaultUnitType] == [self unitType]) {
return 1;
}
if ([self defaultUnitType] == PPPrintSettingsUnitTypeMM) {
return MMsInOneInch;
}
if ([self defaultUnitType] == PPPrintSettingsUnitTypeInch) {
return inchesInOneMM;
}
return 1;
}
- (CGFloat)paperWidth
{
return _paperWidth / [self conversionKoeficientToDefaultUnitType];
}
I forgot that I use NSNumberFormatter with min/max value which where blocking NSTextField to update.

Dynamic Accessibility Label for CALayer

How do I make a CALayer accessible? Specifically, I want the layer to be able to change its label on the fly, since it can change at any time. The official documentation's sample code does not really allow for this.
The following assumes that you have a superview whose layers are all of class AccessableLayer, but if you have a more complex layout this scheme can be modified to handle that.
In order to make a CALayer accessible, you need a parent view that implements the UIAccessibilityContainer methods. Here is one suggested way to do this.
First, have each layer own its UIAccessibilityElement
#interface AccessableLayer : CALayer
#property (nonatomic) UIAccessibilityElement *accessibilityElement;
#end
now in its implementation, you modify the element whenever it changes:
#implementation AccessableLayer
... self.accessibilityElement.accessibilityLabel = text;
#end
The AccessableLayer never creates the UIAccessibilityElement, because the constructor requires a UIAccessibilityContainer. So have the super view create and assign it:
#pragma mark - accessibility
// The container itself is not accessible, so return NO
- (BOOL)isAccessibilityElement
{
return NO;
}
// The following methods are implementations of UIAccessibilityContainer protocol methods.
- (NSInteger)accessibilityElementCount
{
return [self.layer.sublayers count];
}
- (id)accessibilityElementAtIndex:(NSInteger)index
{
AccessableLayer *panel = [self.layer.sublayers objectAtIndex:index];
UIAccessibilityElement *element = panel.accessibilityElement;
if (element == nil) {
element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element.accessibilityFrame = [self convertRect:panel.frame toView:[UIApplication sharedApplication].keyWindow];
element.accessibilityTraits = UIAccessibilityTraitButton;
element.accessibilityHint = #"some hint";
element.accessibilityLabel = #"some text";
panel.accessibilityElement = element;
}
return element;
}
- (NSInteger)indexOfAccessibilityElement:(id)element
{
int numElements = [self accessibilityElementCount];
for (int i = 0; i < numElements; i++) {
if (element == [self accessibilityElementAtIndex:i]) {
return i;
}
}
return NSNotFound;
}

UICollectionView Performing Updates using performBatchUpdates

I have a UICollectionView which I am trying to insert items into it dynamically/with animation. So I have some function that downloads images asynchronously and would like to insert the items in batches.
Once I have my data, I would like to do the following:
[self.collectionView performBatchUpdates:^{
for (UIImage *image in images) {
[self.collectionView insertItemsAtIndexPaths:****]
}
} completion:nil];
Now in place of the ***, I should be passing an array of NSIndexPaths, which should point to the location of the new items to be inserted. I am very confused since after providing the location, how do I provide the actual image that should be displayed at that position?
Thank you
UPDATE:
resultsSize contains the size of the data source array, self.results, before new data is added from the data at newImages.
[self.collectionView performBatchUpdates:^{
int resultsSize = [self.results count];
[self.results addObjectsFromArray:newImages];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
for (int i = resultsSize; i < resultsSize + newImages.count; i++)
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
} completion:nil];
See Inserting, Deleting, and Moving Sections and Items from the "Collection View Programming Guide for iOS":
To insert, delete, or move a single section or item, you must follow
these steps:
Update the data in your data source object.
Call the appropriate method of the collection view to insert or delete the section or item.
It is critical that you update your data source before notifying the
collection view of any changes. The collection view methods assume
that your data source contains the currently correct data. If it does
not, the collection view might receive the wrong set of items from
your data source or ask for items that are not there and crash your
app.
So in your case, you must add an image to the collection view data source first and then call insertItemsAtIndexPaths. The collection view will then ask the data source delegate function to provide the view for the inserted item.
I just implemented that with Swift. So I would like to share my implementation.
First initialise an array of NSBlockOperations:
var blockOperations: [NSBlockOperation] = []
In controller will change, re-init the array:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
blockOperations.removeAll(keepCapacity: false)
}
In the did change object method:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
if type == NSFetchedResultsChangeType.Insert {
println("Insert Object: \(newIndexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.insertItemsAtIndexPaths([newIndexPath!])
}
})
)
}
else if type == NSFetchedResultsChangeType.Update {
println("Update Object: \(indexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.reloadItemsAtIndexPaths([indexPath!])
}
})
)
}
else if type == NSFetchedResultsChangeType.Move {
println("Move Object: \(indexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.moveItemAtIndexPath(indexPath!, toIndexPath: newIndexPath!)
}
})
)
}
else if type == NSFetchedResultsChangeType.Delete {
println("Delete Object: \(indexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.deleteItemsAtIndexPaths([indexPath!])
}
})
)
}
}
In the did change section method:
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
if type == NSFetchedResultsChangeType.Insert {
println("Insert Section: \(sectionIndex)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.insertSections(NSIndexSet(index: sectionIndex))
}
})
)
}
else if type == NSFetchedResultsChangeType.Update {
println("Update Section: \(sectionIndex)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.reloadSections(NSIndexSet(index: sectionIndex))
}
})
)
}
else if type == NSFetchedResultsChangeType.Delete {
println("Delete Section: \(sectionIndex)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.deleteSections(NSIndexSet(index: sectionIndex))
}
})
)
}
}
And finally, in the did controller did change content method:
func controllerDidChangeContent(controller: NSFetchedResultsController) {
collectionView!.performBatchUpdates({ () -> Void in
for operation: NSBlockOperation in self.blockOperations {
operation.start()
}
}, completion: { (finished) -> Void in
self.blockOperations.removeAll(keepCapacity: false)
})
}
I personally added some code in the deinit method as well, in order to cancel the operations when the ViewController is about to get deallocated:
deinit {
// Cancel all block operations when VC deallocates
for operation: NSBlockOperation in blockOperations {
operation.cancel()
}
blockOperations.removeAll(keepCapacity: false)
}
I was facing the similar issue while deleting the item from index and this is what i think we need to do while using performBatchUpdates: method.
1# first call deleteItemAtIndexPath to delete the item from collection view.
2# Delete the element from array.
3# Update collection view by reloading data.
[self.collectionView performBatchUpdates:^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:sender.tag inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
[self.addNewDocumentArray removeObjectAtIndex:sender.tag];
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
This help me to remove all the crash and assertion failures.

How to identify previous view controller in navigation stack

I have 2 seperate navigationcontrollers, one with RootViewController A and the other with RootViewController B.
I am able to push ViewController C onto either A or B's navigation stack.
Question: When I am in ViewController C, how can I find out if I am in the stack belonging to A or B?
You could use the UINavigationController's viewControllers property:
#property(nonatomic, copy) NSArray *viewControllers
Discussion: The root view controller is at index 0 in the array, the back view controller is at index n-2, and the top controller is at index n-1, where n is the number of items in the array.
https://developer.apple.com/documentation/uikit/uinavigationcontroller
You could use that to test whether the root view controller (the one at array index 0) is view controller A or B.
Here's the implementation of the accepted answer:
- (UIViewController *)backViewController
{
NSInteger numberOfViewControllers = self.navigationController.viewControllers.count;
if (numberOfViewControllers < 2)
return nil;
else
return [self.navigationController.viewControllers objectAtIndex:numberOfViewControllers - 2];
}
- (UIViewController *)backViewController
{
NSInteger myIndex = [self.navigationController.viewControllers indexOfObject:self];
if ( myIndex != 0 && myIndex != NSNotFound ) {
return [self.navigationController.viewControllers objectAtIndex:myIndex-1];
} else {
return nil;
}
}
A more general implementation of the accepted answer:
- (UIViewController *)backViewController {
NSArray * stack = self.navigationController.viewControllers;
for (int i=stack.count-1; i > 0; --i)
if (stack[i] == self)
return stack[i-1];
return nil;
}
This will return the correct "back view controller" regardless of where the current class is on the navigation stack.
Swift implementation of tjklemz code as an extension:
extension UIViewController {
func backViewController() -> UIViewController? {
if let stack = self.navigationController?.viewControllers {
for(var i=stack.count-1;i>0;--i) {
if(stack[i] == self) {
return stack[i-1]
}
}
}
return nil
}
}
Here's a modifed version of Prabhu Beeman's Swift code that adapts it to support Swift 3:
extension UIViewController {
func backViewController() -> UIViewController? {
if let stack = self.navigationController?.viewControllers {
for i in (1..<stack.count).reverse() {
if(stack[i] == self) {
return stack[i-1]
}
}
}
return nil
}
}
As a UINavigationController extension:
extension UINavigationController {
func previousViewController() -> UIViewController? {
guard viewControllers.count > 1 else {
return nil
}
return viewControllers[viewControllers.count - 2]
}
}
Access the n-2 element of the viewControllers property to access the parent view controller.
Once you have that instance, you can check its type by logging what comes out of the NSStringFromClass() function. Or you could keep some static const identifier string in controllers A and B, and a getter function that prints out the string.
Swift implementation of #tjklemz code:
var backViewController : UIViewController? {
var stack = self.navigationController!.viewControllers as Array
for (var i = stack.count-1 ; i > 0; --i) {
if (stack[i] as UIViewController == self) {
return stack[i-1] as? UIViewController
}
}
return nil
}
Use the navigationController method to retrieve it. See documentation on Apple's site.
navigationController A parent or ancestor that is a navigation
controller. (read-only)
#property(nonatomic, readonly, retain) UINavigationController
*navigationController
Discussion Only returns a navigation controller if the view
controller is in its stack. This property is nil if a navigation
controller cannot be found.
Availability Available in iOS 2.0 and later.
Find the previous view controller is simple.
In your case, i.e., you are in C and you need B, [self.navigationController.viewControllers lastObject] is what you want.
For A, since A is the root view controller, you can just replace lastObject with firstObject to obtain.
Because dead horses enjoy being beaten :)
- (UIViewController *)previousViewController
{
NSArray *vcStack = self.navigationController.viewControllers;
NSInteger selfIdx = [vcStack indexOfObject:self];
if (vcStack.count < 2 || selfIdx == NSNotFound) { return nil; }
return (UIViewController *)[vcStack objectAtIndex:selfIdx - 1];
}
A more succinct Swift impementation:
extension UIViewController {
var previousViewController: UIViewController? {
guard let nav = self.navigationController,
let myIdx = nav.viewControllers.index(of: self) else {
return nil
}
return myIdx == 0 ? nil : nav.viewControllers[myIdx-1]
}
}
Implementation for Swift 2.2 - Add this in an UIViewController extension. Safe in the sense it will return nil if the viewcontroller is the rootvc or not in a navigationcontroller.
var previousViewController: UIViewController? {
guard let viewControllers = navigationController?.viewControllers else {
return nil
}
var previous: UIViewController?
for vc in viewControllers{
if vc == self {
break
}
previous = vc
}
return previous
}
With Swift using guard.
extension UIViewController {
func getPreviousViewController() -> UIViewController? {
guard let _ = self.navigationController else {
return nil
}
guard let viewControllers = self.navigationController?.viewControllers else {
return nil
}
guard viewControllers.count >= 2 else {
return nil
}
return viewControllers[viewControllers.count - 2]
}
}
My extension, swift 3
extension UIViewController {
var previousViewController: UIViewController? {
guard let controllers = navigationController?.viewControllers, controllers.count > 1 else { return nil }
switch controllers.count {
case 2: return controllers.first
default: return controllers.dropLast(2).first
}
}
}
for swift 3 you can do this:
var backtoViewController:UIViewController!
for viewController in (self.navigationController?.viewControllers)!.reversed() {
if viewController is NameOfMyDestinationViewController {
backtoViewController = viewController
}
}
self.navigationController!.popToViewController(backtoViewController, animated: true)
You only need replace "NameOfMyDestinationViewController" by viewController that you want to return.