What's is a reliable way to detect if user has enabled this API?
CGWindowListCreateImage returns a valid object even if screen recording API is disabled. There are multiple combinations possible (kCGWindowListOptionIncludingWindow, kCGWindowListOptionOnScreenBelowWindow) and only some will return NULL.
- (CGImageRef)createScreenshotImage
{
NSWindow *window = [[self view] window];
NSRect rect = [window frame];
rect.origin.y = NSHeight([[window screen] frame]) - NSMaxY([window frame]);
CGImageRef screenshot = CGWindowListCreateImage(
rect,
kCGWindowListOptionIncludingWindow,
//kCGWindowListOptionOnScreenBelowWindow,
0,//(CGWindowID)[window windowNumber],
kCGWindowImageBoundsIgnoreFraming);//kCGWindowImageDefault
return screenshot;
}
The only reliable way is through CGDisplayStreamCreate which is risky as Apple always changes privacy settings every year.
- (BOOL)canRecordScreen
{
if (#available(macOS 10.15, *)) {
CGDisplayStreamRef stream = CGDisplayStreamCreate(CGMainDisplayID(), 1, 1, kCVPixelFormatType_32BGRA, nil, ^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
;
});
BOOL canRecord = stream != NULL;
if (stream) {
CFRelease(stream);
}
return canRecord;
} else {
return YES;
}
}
All of the solutions presented here have a flaw in one way or another. The root of the problem is that there's no correlation between your permission to know about a window (via the name in the window list), your permission to know about the process owner of the window (such as WindowServer and Dock). Your permission to view the pixels on screen is a combination of two sparse sets of information.
Here is a heuristic that covers all the cases as of macOS 10.15.1:
BOOL canRecordScreen = YES;
if (#available(macOS 10.15, *)) {
canRecordScreen = NO;
NSRunningApplication *runningApplication = NSRunningApplication.currentApplication;
NSNumber *ourProcessIdentifier = [NSNumber numberWithInteger:runningApplication.processIdentifier];
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger numberOfWindows = CFArrayGetCount(windowList);
for (int index = 0; index < numberOfWindows; index++) {
// get information for each window
NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, index);
NSString *windowName = windowInfo[(id)kCGWindowName];
NSNumber *processIdentifier = windowInfo[(id)kCGWindowOwnerPID];
// don't check windows owned by this process
if (! [processIdentifier isEqual:ourProcessIdentifier]) {
// get process information for each window
pid_t pid = processIdentifier.intValue;
NSRunningApplication *windowRunningApplication = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
if (! windowRunningApplication) {
// ignore processes we don't have access to, such as WindowServer, which manages the windows named "Menubar" and "Backstop Menubar"
}
else {
NSString *windowExecutableName = windowRunningApplication.executableURL.lastPathComponent;
if (windowName) {
if ([windowExecutableName isEqual:#"Dock"]) {
// ignore the Dock, which provides the desktop picture
}
else {
canRecordScreen = YES;
break;
}
}
}
}
}
CFRelease(windowList);
}
If canRecordScreen is not set, you'll need to put up some kind of dialog that warns the user that they'll only be able to see the menubar, desktop picture, and the app's own windows. Here's how we presented it in our app xScope.
And yes, I'm still bitter that these protections were introduced with little regard to usability.
Apple provides direct low level api to check for access and grant access. No need to use tricky workarounds.
/* Checks whether the current process already has screen capture access */
#available(macOS 10.15, *)
public func CGPreflightScreenCaptureAccess() -> Bool
Use the above functions to check for screen capture access.
if access is not given use the below function to prompt for access
/* Requests event listening access if absent, potentially prompting */
#available(macOS 10.15, *)
public func CGRequestScreenCaptureAccess() -> Bool
Screenshot taken from documentation
#marek-h posted a good example that can detect the screen recording setting without showing privacy alert.
Btw, #jordan-h mentioned that this solution doesn't work when the app presents an alert via beginSheetModalForWindow.
I found that SystemUIServer process is always creating some windows with names: AppleVolumeExtra, AppleClockExtra, AppleBluetoothExtra ...
We can't get the names of these windows, before the screen recording is enabled in Privacy preferences. And when we can get one of these names at least, then it means that the user has enabled screen recording.
So we can check the names of the windows (created by SystemUIServer process) to detect the screen recording preference, and it works fine on macOS Catalina.
#include <AppKit/AppKit.h>
#include <libproc.h>
bool isScreenRecordingEnabled()
{
if (#available(macos 10.15, *)) {
bool bRet = false;
CFArrayRef list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
if (list) {
int n = (int)(CFArrayGetCount(list));
for (int i = 0; i < n; i++) {
NSDictionary* info = (NSDictionary*)(CFArrayGetValueAtIndex(list, (CFIndex)i));
NSString* name = info[(id)kCGWindowName];
NSNumber* pid = info[(id)kCGWindowOwnerPID];
if (pid != nil && name != nil) {
int nPid = [pid intValue];
char path[PROC_PIDPATHINFO_MAXSIZE+1];
int lenPath = proc_pidpath(nPid, path, PROC_PIDPATHINFO_MAXSIZE);
if (lenPath > 0) {
path[lenPath] = 0;
if (strcmp(path, "/System/Library/CoreServices/SystemUIServer.app/Contents/MacOS/SystemUIServer") == 0) {
bRet = true;
break;
}
}
}
}
CFRelease(list);
}
return bRet;
} else {
return true;
}
}
I'm not aware of an API that's specifically for getting the screen recording permission status. Besides creating a CGDisplayStream and checking for nil, the Advances in macOS Security WWDC presentation also mentioned that certain metadata from the CGWindowListCopyWindowInfo() API will not be returned unless permission is granted. So something like this does seem to work, although it has the same issue of relying on implementation details of that function:
private func canRecordScreen() -> Bool {
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: AnyObject]] else { return false }
return windows.allSatisfy({ window in
let windowName = window[kCGWindowName as String] as? String
return windowName != nil
})
}
As of Nov19 chockenberry has correct answer.
As #onelittlefish pointed out the kCGWindowName is being omitted in case user has not enabled the screen recording access in privacy pane. This method also doesn't trigger the privacy alert.
- (BOOL)canRecordScreen
{
if (#available(macOS 10.15, *)) {
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger numberOfWindows = CFArrayGetCount(windowList);
NSUInteger numberOfWindowsWithName = 0;
for (int idx = 0; idx < numberOfWindows; idx++) {
NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
NSString *windowName = windowInfo[(id)kCGWindowName];
if (windowName) {
numberOfWindowsWithName++;
} else {
//no kCGWindowName detected -> not enabled
break; //breaking early, numberOfWindowsWithName not increased
}
}
CFRelease(windowList);
return numberOfWindows == numberOfWindowsWithName;
}
return YES;
}
The most favorable answer is not exactly right, he left out some sences, like sharing state.
we can find the answer in WWDC(https://developer.apple.com/videos/play/wwdc2019/701/?time=1007)
Here are some excerpts from WWDC:
the window name and sharing state are not available, unless the user has preapproved the app for screen recording. And this is because some apps put sensitive data such as account names or more likely web page URLs in the window's name.
- (BOOL)ScreeningRecordPermissionCheck {
if (#available(macOS 10.15, *)) {
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger numberOfWindows = CFArrayGetCount(windowList);
NSUInteger numberOfWindowsWithInfoGet = 0;
for (int idx = 0; idx < numberOfWindows; idx++) {
NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
NSString *windowName = windowInfo[(id)kCGWindowName];
NSNumber* sharingType = windowInfo[(id)kCGWindowSharingState];
if (windowName || kCGWindowSharingNone != sharingType.intValue) {
numberOfWindowsWithInfoGet++;
} else {
NSNumber* pid = windowInfo[(id)kCGWindowOwnerPID];
NSString* appName = windowInfo[(id)kCGWindowOwnerName];
NSLog(#"windowInfo get Fail pid:%lu appName:%#", pid.integerValue, appName);
}
}
CFRelease(windowList);
if (numberOfWindows == numberOfWindowsWithInfoGet) {
return YES;
} else {
return NO;
}
}
return YES;
}
As of MacOS 10.15.7 the heuristics of obtaining window-names for visible windows, and so know we have screen-capture permission, doesn't always work. Sometimes we just don't find valid windows we can query, and would wrongly deduce we don't have permissions.
However, I found another way to directly query (using sqlite) the Apple TCC database - the model where permissions are persisted. The screen-recording permissions are to be found in the "System level" TCC database ( residing in /Library/Application Support/com.apple.TCC/TCC.db). If you open the database using sqlite, and query: SELECT allowed FROM access WHERE client="com.myCompany.myApp" AND service="kTCCServiceScreenCapture" you'll get your answer.
Two downsides comparing to other answers:
to open this TCC.db database, your app must have "Full Disk Access" permission. It doesn't need to run with 'root' privileges, and root privileges won't help if you don't have the "Full disk access".
it takes about 15 millisec to run, which is slower than querying the window list.
The up side -- it's a direct query of the actual thing, and does not rely on any windows, or processes to exist at the time of query.
Here's some draft code to do this:
NSString *client = #"com.myCompany.myApp";
sqlite3 *tccDb = NULL;
sqlite3_stmt *statement = NULL;
NSString *pathToSystemTCCDB = #"/Library/Application Support/com.apple.TCC/TCC.db";
const char *pathToDBFile = [pathToSystemTCCDB fileSystemRepresentation];
if (sqlite3_open(pathToDBFile, &tccDb) != SQLITE_OK)
return nil;
const char *query = [[NSString stringWithFormat: #"SELECT allowed FROM access WHERE client=\"%#\" AND service=\"kTCCServiceScreenCapture\"",client] UTF8String];
if (sqlite3_prepare_v2(tccDb, query , -1, &statement, nil) != SQLITE_OK)
return nil;
BOOL allowed = NO;
while (sqlite3_step(statement) == SQLITE_ROW)
allowed |= (sqlite3_column_int(statement, 0) == 1);
if (statement)
sqlite3_finalize(statement);
if (tccDb)
sqlite3_close(tccDb);
return #(allowed);
}
Working for me.
Code from: https://gist.github.com/code4you2021/270859c71f90720d880ccb2474f4e7df
import Cocoa
struct ScreenRecordPermission {
static var hasPermission: Bool {
permissionCheck()
}
static func permissionCheck() -> Bool {
if #available(macOS 10.15, *) {
let runningApplication = NSRunningApplication.current
let processIdentifier = runningApplication.processIdentifier
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID)
as? [[String: AnyObject]],
let _ = windows.first(where: { window -> Bool in
guard let windowProcessIdentifier = (window[kCGWindowOwnerPID as String] as? Int).flatMap(pid_t.init),
windowProcessIdentifier != processIdentifier,
let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier),
windowRunningApplication.executableURL?.lastPathComponent != "Dock",
let _ = window[String(kCGWindowName)] as? String
else {
return false
}
return true
})
else {
return false
}
}
return true
}
static func requestPermission() {
if #available(macOS 10.15, *) {
CGWindowListCreateImage(CGRect(x: 0, y: 0, width: 1, height: 1), .optionOnScreenOnly, kCGNullWindowID, [])
}
}
}
# how to use
# print("hasPermission: ", ScreenRecordPermission.hasPermission)
The above answer is not working fine. Below is the correct answer.
private var canRecordScreen : Bool {
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: AnyObject]] else { return false }
return windows.allSatisfy({ window in
let windowName = window[kCGWindowName as String] as? String
let isSharingEnabled = window[kCGWindowSharingState as String] as? Int
return windowName != nil || isSharingEnabled == 1
})
}
Hi currently my game is loading fl_gfood.png or fl_bfood.png from the fl_food.plist. That works fine but now I have 17 bad food items .png and 17 good food items .png. My question is how do I randomly display one of the 17 items from each group? Can I just do a wildcard for the sprite png file name? See code comment below. Also a suggestion was made that I could possibly load the .plist file names into an array and randomly pick a name, how would that be done.
#implementation Food
+ (void)loadAssets {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[FLSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:[AssetHelper getDeviceSpecificFileNameFor:#"fl_food.plist"]];
});
}
- (id)init {
self = [super init];
if (self) {
self.size = CGSizeMake(16 * __HIGHRES_SCALE, 16 * __HIGHRES_SCALE);
self.offset = CGPointMake(8 * __HIGHRES_SCALE, 8 * __HIGHRES_SCALE);
self.removeAfterCollision = YES;
self.collideable = NO;
self.score = 10;
}
return self;
}
- (FLSprite *)sprite {
if(_sprite == nil) {
if(self.score < 10) {
_sprite = [FLSprite spriteWithSpriteFrameName:#"fl_gfood.png"]; // fl_gfood_*.png ... Can I do something like this?
} else {
_sprite = [FLSprite spriteWithSpriteFrameName:#"fl_bfood.png"];
}
}
return _sprite;
}
I would use arc4random() and stringByAppendingString
- (FLSprite *)sprite {
if(_sprite == nil) {
NSString *disFood;
disFood = (self.score < 10) ? #"fl_gfood" : #"fl_bfood";
//random # between 1 - 10
int randNum = (arc4random() % 10)+1;
NSString *formattedName = [NSString stringWithFormat:#"_%i.png",randNum];
disFood = [disFood stringByAppendingString:formattedName];
_sprite = [FLSprite spriteWithSpriteFrameName:disFood];
NSLog(disFood);//Logging disFood String;
}
return _sprite;
}
How do I restrict a NSTextField to allow only numbers/integers? I've found questions like this one, but they didn't help!
Try to make your own NSNumberFormatter subclass and check the input value in -isPartialStringValid:newEditingString:errorDescription: method.
#interface OnlyIntegerValueFormatter : NSNumberFormatter
#end
#implementation OnlyIntegerValueFormatter
- (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
{
if([partialString length] == 0) {
return YES;
}
NSScanner* scanner = [NSScanner scannerWithString:partialString];
if(!([scanner scanInt:0] && [scanner isAtEnd])) {
NSBeep();
return NO;
}
return YES;
}
#end
And then set this formatter to your NSTextField:
OnlyIntegerValueFormatter *formatter = [[[OnlyIntegerValueFormatter alloc] init] autorelease];
[textField setFormatter:formatter];
Swift 3 Version
import Foundation
class OnlyIntegerValueFormatter: NumberFormatter {
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
// Ability to reset your field (otherwise you can't delete the content)
// You can check if the field is empty later
if partialString.isEmpty {
return true
}
// Optional: limit input length
/*
if partialString.characters.count>3 {
return false
}
*/
// Actual check
return Int(partialString) != nil
}
}
Use:
let onlyIntFormatter = OnlyIntegerValueFormatter()
myNsTextField.formatter = onlyIntFormatter
Here's a solution with filtering. Give a delegate and an outlet to textfield and set controlTextDidChange method.
- (void)controlTextDidChange:(NSNotification *)aNotification {
NSTextField *textfield = [notification object];
NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
char *stringResult = malloc([textfield.stringValue length]);
int cpt=0;
for (int i = 0; i < [textfield.stringValue length]; i++) {
unichar c = [textfield.stringValue characterAtIndex:i];
if ([charSet characterIsMember:c]) {
stringResult[cpt]=c;
cpt++;
}
}
stringResult[cpt]='\0';
textfield.stringValue = [NSString stringWithUTF8String:stringResult];
free(stringResult);
}
Try this -
NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[textField setFormatter:formatter];
Here is a Swift version:
override func isPartialStringValid(partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>) -> Bool {
if (count(partialString.utf16)) {
return true
}
if (partialString.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
NSBeep()
return false
}
return true
}
In SWIFT, I do it this way
Convert the text value to Int with Int()
Check the converted value is not less than 0
If less than 0, display error message other accept the value
if ((Int(txtField.stringValue)) < 0){
// Display error message
}
[Works with Swift 3.0.1]
As others suggested, subclass NumberFormatter and override isPartialStringValid method. The easiest way is to drop a NumberFormatter object under your NSTextField in xib/storyboard and update it's Custom Class.
Next implementation allows only integers or blank value and plays a beep if string contains illegal characters.
class IntegerFormatter: NumberFormatter {
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
// Allow blank value
if partialString.numberOfCharacters() == 0 {
return true
}
// Validate string if it's an int
if partialString.isInt() {
return true
} else {
NSBeep()
return false
}
}
}
String's numberOfCharacters() and isInt() are methods added in an extension.
extension String {
func isInt() -> Bool {
if let intValue = Int(self) {
if intValue >= 0 {
return true
}
}
return false
}
func numberOfCharacters() -> Int {
return self.characters.count
}
}
Here is the steps to create the same....
Just create the ANYCLASS(called SAMPLE) with sub classing the NSNumberFormatter ...
in .m file write the following code...
- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error {
// Make sure we clear newString and error to ensure old values aren't being used
if (newString) { *newString = nil;}
if (error) {*error = nil;}
static NSCharacterSet *nonDecimalCharacters = nil;
if (nonDecimalCharacters == nil) {
nonDecimalCharacters = [[NSCharacterSet decimalDigitCharacterSet] invertedSet] ;
}
if ([partialString length] == 0) {
return YES; // The empty string is okay (the user might just be deleting everything and starting over)
} else if ([partialString rangeOfCharacterFromSet:nonDecimalCharacters].location != NSNotFound) {
return NO; // Non-decimal characters aren't cool!
}
return YES;
}
Now.. in your Actual Class set the formatter to your NSTextField object like below...
NSTextField *mySampleTxtFld;
for this set the Formatter...
SAMPLE* formatter=[[SAMPLE alloc]init];// create SAMPLE FORMATTER OBJECT
self.mySampleTxtFld.delegate=self;
[self.mySampleTxtFld setFormatter:formatter];
Your done!!!
Swift 2.0 custom formatter with 0 instead of empty space :
class OnlyIntegerValueFormatter: NSNumberFormatter {
override func isPartialStringValid(partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>) -> Bool {
if partialString.isEmpty {
newString.memory = "0"
return false
}
if Int(partialString) < 0 {
NSBeep()
return false
} else {
return true
}
}
}
// NSTextFieldNumberFormatter+Extension.swift
import Foundation
class TextFieldIntegerValueFormatter: NumberFormatter {
var maxLength: Int
init(maxLength: Int) {
self.maxLength = maxLength
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
// Ability to reset your field (otherwise you can't delete the content)
// You can check if the field is empty later
if partialString.isEmpty {
return true
}
// Optional: limit input length
if partialString.count > maxLength {
return false
}
// Actual check
return Int(partialString) != nil
}
}
//Need to call like:
myNsTextField.formatter = TextFieldIntegerValueFormatter(maxLength: 6)
Is there a way to block some keyboard layouts (input sources) in NSTextField.
I need to block all non-romans languages such as Russian, Belorussian, Ukraine and etc or disable all languages and enable only English/Deutsch language.
If it will be not so hard - make some example please.
UPD:
I think i need to use this
but how? =)
Checking just the keyboard attached is maybe a bit flakey. With the Option key you can input a lot non-Roman characters from any keyboard, for instance. Not to mention copy and paste.
A better approach would be to make a subclass of NSFormatter and implement isPartialStringValid:proposedSelectedRange:originalString:originalSelectedRange:errorDescription:
A simple implementation could be something like this:
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error
{
NSString *partialString = *partialStringPtr;
NSCharacterSet *acceptedCharacters = [NSCharacterSet characterSetWithCharactersInString: #"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
NSCharacterSet *notAcceptedCharacters = [acceptedCharacters invertedSet];
BOOL needsCheck = YES;
BOOL didChange = NO;
do {
NSRange rng = [partialString rangeOfCharacterFromSet:notAcceptedCharacters];
if ( !NSEqualRanges(rng, NSMakeRange(NSNotFound, 0)) ) {
partialString = [partialString stringByReplacingCharactersInRange:rng withString:#""];
didChange = YES;
}
else {
needsCheck = NO;
}
} while (needsCheck);
if ( didChange ) {
*partialStringPtr = partialString;
NSRange newRange = origSelRange;
newRange.length = 0;
*proposedSelRangePtr =newRange;
return NO;
}
return YES;
}
When subclassing NSFormatter you are also required to implement stringForObjectValue: and getObjectValue:forString:errorDescription:, but since you are inputting a string, they can just pass the input string straight through.
I'm using Apple TapDetectingImageView class from Autoscroll example.
Static analizer shows the following warning:
Classes/TapDetectingImageView.m:68:30:{68:17-68:29}:
warning:
The left operand of '==' is a garbage value
if (tapCounts[0] == 1 && tapCounts[1] == 1) {
~~~~~~~~~~~~ ^
for the attached code below. Garbage value occurs when a variable is read without having been initialized first. But it seems tapCounts is initialized already.
Can I ignore it if the app is running fine or should I modify anything?
BOOL allTouchesEnded = ([touches count] == [[event touchesForView:self] count]);
// first check for plain single/double tap, which is only possible if we haven't seen multiple touches
if (!multipleTouches) {
UITouch *touch = [touches anyObject];
tapLocation = [touch locationInView:self];
if ([touch tapCount] == 1) {
[self performSelector:#selector(handleSingleTap) withObject:nil afterDelay:DOUBLE_TAP_DELAY];
} else if([touch tapCount] == 2) {
[self handleDoubleTap];
}
}
// check for 2-finger tap if we've seen multiple touches and haven't yet ruled out that possibility
else if (multipleTouches && twoFingerTapIsPossible) {
// case 1: this is the end of both touches at once
if ([touches count] == 2 && allTouchesEnded) {
int i = 0;
int tapCounts[2]; CGPoint tapLocations[2];
for (UITouch *touch in touches) {
tapCounts[i] = [touch tapCount];
tapLocations[i] = [touch locationInView:self];
i++;
}
if (tapCounts[0] == 1 && tapCounts[1] == 1) { // it's a two-finger tap if they're both single taps
tapLocation = midpointBetweenPoints(tapLocations[0], tapLocations[1]);
[self handleTwoFingerTap];
}
}
This occurs when a variable is created but NOT initialized to a value. In the code above you do this:
int tapCounts[2];
...
But then don't initialize the array contents to anything before attempting to evaluate it in the if statement a few lines down:
if( tapCounts[0] == 1 && tapCounts[1] == 1 ) {
....
}
The compiler is warning you that the contents of tapCounts[0] is not initialized and is garbage.
It's due to tapCounts array is initialized with garbage values for index 0 and 1.
Please change the line:
int tapCounts[2]; CGPoint tapLocations[2];
with
int tapCounts[2] = {0,0}; CGPoint tapLocations[2] = {0,0};
This will remove the initialize warning while analyzing build