This is a question related to Are there APIs for custom vibrations in iOS?.
I am able to create custom vibration patterns, but have no control over the intensity.
This is copied over from Kevin Cao's answer that enables custom vibration patterns:
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSMutableArray* arr = [NSMutableArray array ];
[arr addObject:[NSNumber numberWithBool:YES]]; //vibrate for 2000ms
[arr addObject:[NSNumber numberWithInt:2000]];
[arr addObject:[NSNumber numberWithBool:NO]]; //stop for 1000ms
[arr addObject:[NSNumber numberWithInt:1000]];
[arr addObject:[NSNumber numberWithBool:YES]]; //vibrate for 1000ms
[arr addObject:[NSNumber numberWithInt:1000]];
[arr addObject:[NSNumber numberWithBool:NO]]; //stop for 500ms
[arr addObject:[NSNumber numberWithInt:500]];
[dict setObject:arr forKey:#"VibePattern"];
[dict setObject:[NSNumber numberWithInt:1] forKey:#"Intensity"];
AudioServicesPlaySystemSoundWithVibration(4095,nil,dict);
The line of code that adds the key #"Intensity" with an int value doesn't do the trick and I don't know how to look inside the AudioServicesPlaySystemSoundWithVibration method to figure it out. What do I have to pass to it so that it actually changes the intensity?
Right now, it doesn't matter if I pass 1, 1000, 0.4 or 0.0001, it's always the same intensity (on an iPhone 4 with iOS7). Can anyone recreate this?
I would like to be able not only to create vibration patterns, but a smooth vibration envelope. How to?
(As this is a research project for instrument design, I am not (yet) concerned with the App store restrictions.)
Change the numberWithInt call into numberWithFloat, and change the intensity so it's between 0 and 1. I thought it was weird when they used an int rather than a float.
Edit: Here's a copy/paste that should work for your code to invoke custom vibration:
#pragma mark - Custom vibration methods
-(void)invokeCustomVibrationWithStartStopTimes:(NSArray*)startStopTimes andIntensity:(float)intensity {
BOOL startOrStop = YES;
NSMutableArray* arr = [#[] mutableCopy];
double time = 0;
for (NSNumber *x in stopStartTimes) {
[arr addObject:x]
startOrStop = !startOrStop;
[arr addObject:#(startOrStop)];
time = [x doubleValue] / 1000.0;
}
AudioServicesPlaySystemSoundWithVibration(4095,nil,{#"VibePattern":arr,#"Intensity":#(intensity)})
[self performSelector:#selector(stop) withObject:nil afterDelay:time];
}
-(void)stop {
AudioServicesStopSystemSound(4095); // stop buzzing the phone
}
For startStopTimes, it should alternate between times started and times stopped. Passing in this array:
#[#(2000), #(1000), #(1000), #(500)]
Will do what the example code did. In this case, it will start for 2000 ms, stop for 1000 ms, start for 1000 ms, and stop for 500 ms.
stop is called to stop the sound. The way I have it set up, it stops sounds after the total amount of time sent in.
You may have noticed I've been using array/number literals rather than using [NSArray arrayWithObjects: ... , nil]; or [NSNumber numberWith...];. This makes your code a lot shorter. Also, I marked the beginning with a #pragma mark. Use that to organize it better. Hope it helps!
Related
I am following a tutorial from Ray Wenderlich that involves creating a quiz that can be used on an Apple TV to display the question, and the devices to answer. In this, little time is spent on the logic of answering the questions. The questions are stored in a plist file, where the first possible answer is always the correct one. In code, it is shown as:
- (void)startQuestion
{
// 1
int questionIndex = arc4random_uniform((int)[self.questions count]);
NSMutableArray *questionArray = [self.questions[questionIndex] mutableCopy];
[self.questions removeObjectAtIndex:questionIndex];
// 2
NSString *question = questionArray[0];
[questionArray removeObjectAtIndex:0];
// 3
NSMutableArray *answers = [[NSMutableArray alloc] initWithCapacity:[questionArray count]];
self.currentQuestionAnswer = -1;
self.currentQuestionAnswersReceived = 0;
while ([questionArray count] > 0)
{
// 4
int answerIndex = arc4random_uniform((int)[questionArray count]);
if (answerIndex == 0 && self.currentQuestionAnswer == -1)
{
self.currentQuestionAnswer = [answers count];
}
[answers addObject:questionArray[answerIndex]];
[questionArray removeObjectAtIndex:answerIndex];
}
// 5
[self sendToAllPeers:[kCommandQuestion stringByAppendingString:
[NSString stringWithFormat:#"%lu", (unsigned long)[answers count]]]];
[self.scene startQuestionWithAnswerCount:[answers count]];
[self.mirroredScene startQuestion:question withAnswers:answers];
}
When the app is ran at the end of the tutorial, it always makes 'A' the correct answer. Is there some way to tell the app that the first item under each question in the plist is the correct one, but have it randomize between ABCD so that each time the app is ran the answers are a different choice?
I'm working on an application for the iPhone and I'm keeping track of the users current location. When the didupdateLocations delegate method actually executes i would like to test if the location in the NSArray is in a predefined array the contains other locations, this array mind you could grow over time.
I'm running a for loop within this method to test against my own array of locations but i would like to move that to a separate thread. So in case my own array with multiple locations grows to a large number the for loop does not freeze my UI.
I have tried it like this but I'm getting undesirable results. I understand that the location tracking definitely happens in a separate thread. However those didupdateLocations execute on a separate thread. The Apple doc's are not very clear on the matter. My end goal again is to compare against my array and not lock the UI.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)thisLocation {
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// get the last object in the array of locations
CLLocation* location = [thisLocation lastObject];
dispatch_async(queue, ^{
[self checkmyArray:location];
});
}
-(void)checkmyArray:(CLLocation *)workingLocation{
NSLog(#"SoundTheAlarm");
int alarm_on_c = 0;
NSUInteger tmp_count = [theData.LocationsObjectArray count];
BOOL alarm;
NSMutableDictionary * tempObject;
CLLocationDistance distance = 0.0;
for (int i = 0; i < tmp_count; i++) {
tempObject= [theData.LocationsObjectArray objectAtIndex:i];
thisLoc = [[tempObject objectForKey:#"onoff"] isEqual:#YES];
if (thisLoc) {
//check if we are near that location
double lat = [[tempObject objectForKey:#"latitude"] doubleValue];
double lon = [[tempObject objectForKey:#"longitude"] doubleValue];
// goal = [[CLLocation alloc] initWithLatitude:40.097771 longitude:-74.941399];
goal = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
// check the destination between current location and goal location - in meters
distance = [goal distanceFromLocation:workingLocation];
NSLog(#"distance %f\n", distance);
}
// if distance from goal is less than 350 meters
if (distance <= 350){
[self scheduleNotification:[tempObject objectForKey:#"name"]];
// turn off tracking for this location
[tempObject setObject:#NO forKey:#"onoff"];
[theData.LocationsObjectArray replaceObjectAtIndex:i withObject:tempObject];
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
ExtendedSavedCellTableViewCell *cell = (ExtendedSavedCellTableViewCell *)[self.tableView cellForRowAtIndexPath:path];
cell.switchView.on = NO;
// save the update to the switch to the database as well
NSString *lat = [tempObject objectForKey:#"latitude"];
/*check to determine if the uiswitch is turned off or on.*/
[self fetchedResultsController:#NO lat:lat index:path];
[self displayAlertViewForAlarm:[tempObject objectForKey:#"name"]];
}
-(void)displayAlertViewForAlarm:(NSString *)nameOfLocation{
dispatch_async(dispatch_get_main_queue(), ^(void) {
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Destination reached"
message:nameOfLocation
delegate:self
cancelButtonTitle:#"Go Away"
otherButtonTitles:#"Notify", nil];
[myAlert show];
});
}
It's generally a bad idea to use threads in iOS if you can at all avoid it. In your case, I'd implement the function which does the looping to automatically pop out of the loop after too many iterations and then schedule the next chunk of iterating to happen in another pass through the event loop. In other words, something like this:
- (void) checkLocationsStartingAt:(NSNumber)start
{
NSInteger s = (start) ? [start intValue] : 0;
for (i = s; i < list.length; i++) {
if (i > TOO_MANY) {
[self performSelector:#selector(checkLocationsStartingAt:)
withObject:#(i)
afterDelay:0.001];
return;
} else {
// check element at i
}
}
}
see: NSObject Reference
You are checking one location against an array of other locations.
You display an alert for time you are closer than 350m from a location.
You could be within 350m of many items in the list.
You have no code prevent multiple alerts.
So you will sometimes have many alerts.
You have a couple options. One will probably suit your needs better than the others. Possibly one I didn't list.
You could re-use a single UIAlertView instance and check the
visible property on it. If it's already visible, do nothing.
You could break out of the loop once you get a single "hit" on being
<350m.
You could stick in a BOOL which records if you've shown the alert and
check the BOOL every time before you show the alert again.
I have a problem with my app, where it consumes a lot of memory and crashes after about 40 minutes. I have run instruments to see if there were any leaks, but none appeared. Inside the app, I am using Mapbox to display maps and drawing a line annotation wherever the user has travelled. I am storing these locations in an array, removing the current annotation and presenting a new one whenever the user has moved.
In my MapViewController.m
- (void)drawCurrentPolyline
{
[self removePolylineForTrip:locationServices.trip forClass:nil];
[self addPolylineForTrip:locationServices.trip forClass:nil];
globalData.locationData = [[NSMutableArray alloc] init];
colour = [globalData convertColourFromString:[globalData.currentTrip objectForKey:#"colour"]];
[colourArray addObject:colour];
if ([currentLocations count] >= 2)
{
for (int i = [currentLocations count] - 2; i <= [currentLocations count] - 1; i++)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[currentLocations[i] objectForKey:#"latitude"] doubleValue] longitude:[[currentLocations[i] objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
}
else
{
for (NSDictionary *location in currentLocations)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[location objectForKey:#"latitude"] doubleValue] longitude:[[location objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
}
}
- (void)removePolylineForTrip:(int)trip forClass:(MapPreferencesTableViewController *)mapPreferencesTableViewController
{
NSMutableDictionary *dictionary = [tripAnnotions objectForKey:[NSString stringWithFormat:#"trip%d", trip]];
NSMutableArray *annotationArray = [dictionary objectForKey:#"annotions"];
for (RMAnnotation *annotation in annotationArray)
{
if ([[annotation.userInfo objectForKey:#"type"] isEqualToString:#"line"])
{
[mapView removeAnnotation:annotation];
}
}
}
- (void)addPolylineForTrip:(int)trip forClass:(MapPreferencesTableViewController *)mapPreferencesTableViewController
{
globalData.locationData = [[NSMutableArray alloc] init];
globalData.tripData = [globalData.trips objectForKey:[NSString stringWithFormat:#"trip%d", trip]];
NSMutableArray *locationArray = [globalData.tripData objectForKey:#"locationData"];
colour = [globalData convertColourFromString:[globalData.tripData objectForKey:#"colour"]];
[colourArray addObject:colour];
double lineWidth = 5.0;
NSString *type = #"line";
for (NSDictionary *location in locationArray)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[location objectForKey:#"latitude"] doubleValue] longitude:[[location objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
if ([locationArray count] > 1)
{
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:mapView
coordinate:((CLLocation *)[globalData.locationData objectAtIndex:0]).coordinate
andTitle:[NSString stringWithFormat:#"Trip %d", trip]];
NSMutableDictionary *annotationData = [[NSMutableDictionary alloc] init];
[annotationData setObject:globalData.locationData forKey:#"locations"];
[annotationData setObject:colour forKey:#"colour"];
[annotationData setObject:[NSString stringWithFormat:#"%f", lineWidth] forKey:#"width"];
[annotationData setObject:type forKey:#"type"];
[annotationData setObject:[NSString stringWithFormat:#"%d", trip] forKey:#"trip"];
annotation.userInfo = annotationData;
// NSLog(#"User Info For Annotation:\n%#", annotation.userInfo);
//annotation.layer = [NSString stringWithFormat:#"%d", trip];
[annotation setBoundingBoxFromLocations:globalData.locationData];
// Add annotation for tracking
[self addAnnotationToTripArray:annotation forTrip:trip];
[mapView addAnnotation:annotation];
}
}
I think the problem might be the fact that I am storing each of the user locations in a mutable array. Hence, I told the app to remove everything in the array once a memory warning has appeared. However, the app still crashes. I cannot seem to find the source of the memory consumption. There are no leaks (except for some small core graphics ones).
Could someone please guide me from where to go from here, or help me find the source of this issue? The simulator used over 1GB of data, and the app became very slow.
Remember, it's not a leak if it's not memory that's been orphaned somehow. Pay attention to your memory usage over time. Instruments will tell you the number of objects living and what kind they are. Things to look out for are things like large arrays of things growing over time and UIView's not getting removed from the superViews.
I had a similar issue and it was because I had MBProgressHUD instances that were hidden, but never removed from their superView.
As #InkGolem states, increasing memory is not necessarily a leak.
Use instruments to check for leaks and memory loss due to retained but not leaked memory. The latter is unused memory that is still pointed to. Use Mark Generation (Heapshot) in the Allocations instrument on Instruments.
For HowTo use Heapshot to find memory creap, see: bbum blog
Basically there method is to run Instruments allocate tool, take a heapshot, run an intuition of your code and another heapshot repeating 3 or 4 times. This will indicate memory that is allocated and not released during the iterations.
To figure out the results disclose to see the individual allocations.
If you need to see where retains, releases and autoreleases occur for an object use instruments:
Run in instruments, in Allocations set "Record reference counts" on on (you have to stop recording to set the option). Cause the picker to run, stop recording, search for there ivar (datePickerView), drill down and you will be able to see where all retains, releases and autoreleases occurred.
So I have the following code, that splits up the street,town,state,country,zip and makes an AddressAnnotation, a class I have extended from annotation.
Sometimes all of the pins show up on the map and sometimes it cannot find some of the places, although it is a collection of the exact same addresses every time. Is there any reason why Google maps can find a place sometimes but not all the time?
Is it something in my code?
Thanks,
R
for (int i=0; i < [places count]-1; ++i) {
NSArray *arrayPlace = [[places objectAtIndex:i] componentsSeparatedByString:#"***"];
AddressAnnotation *addAnnotation = [[AddressAnnotation alloc] initWithCoordinate:
[self getLocationFromAddressString:
[NSString stringWithFormat:
#"%# %# %# %# %# %#",
[arrayPlace objectAtIndex:3],
[arrayPlace objectAtIndex:4],
[arrayPlace objectAtIndex:5],
[arrayPlace objectAtIndex:6],
[arrayPlace objectAtIndex:7],
[arrayPlace objectAtIndex:8]]]];
[addAnnotation setWebsite:[arrayPlace objectAtIndex:2]];
[addAnnotation setTitle:[arrayPlace objectAtIndex:0]];
[addAnnotation setSubtitle:[arrayPlace objectAtIndex:1]];
[mapView addAnnotation:addAnnotation];
}
Try logging the address strings you are generating. If they are always the same then look into getLocationFromAddressString and see if the coordinates it is returning are always the same. If not then that function has the problem. I've looked around for that function but Google just keeps bringing me back to StackOverflow. Is it your own function or have you used the one from this question? MKMapView with address
Shouldn't it be in the first line something like this?
for (int i=0; i < [places count]; i++) {
or better just:
for (NSArray *arrayPlace in places) {
In an app that has many different UITableViews, I have found myself frequently using temporary arrays to import the data used to populate the table view, determine number of rows, sections, headers, footers, etc. I'm wondering if, because these array need to be created for each cell in the table over and over if declaring the static so they don't need to be created again will help performance, because right now these arrays are being created in cellForRowAtIndexPath:, numberOfRowsInSections:, numberOfSectionsInTableView:,footerForSection:`. Would declaring this many static arrays (which might contain a decent amount of information, say a couple thousand doubles and a couple hundred strings), in the long run, help or hurt me? I know that a static array stays in memory for the course of the application's life, so will this many static arrays be detrimental? Assuming that this process occurs in 4-5 view controllers across the course of the app, we are talking about 15-20 copies of this array just sitting around. What is my best option here? Thanks
Edit: I am using a singleton which holds the values. The real reason for the temporary arrays is to keep the code clean. I can do something like
dataArray = [[SingletonDataController sharedSingleton] dataArray]
objectAtIndex:CURRENTLY_SELECTED_DATA_INDEX;
then
myTitleString = [dataArray objectAtIndex:keyTitleStringIndexKey];
instead of grouping it all into one unreadable statement like:
myTitleString = [[[[SingletonDataController sharedSingleton] dataArray]
objectAtIndex:CURRENTLY_SELECTED_INDEX] objectAtIndex:keyTitleStringIndexKey];
I have performed some tests of my own, comparing the time it takes to create the table view with/without static initialization. These are the results:
2012-01-29 18:31:57.539 XXXXXXX[711:707] static average: 0.058798
2012-01-29 18:31:57.543 XXXXXXX[711:707] nonstatic average: 0.058395
As you can see, the static initialization is actually slower than the non-static, but only by a few ten-thousandths of a second. This is probably just a product of inaccurate measurement, but the results say enough to convince me that the difference is small enough to dismiss. Mystery solved.
When you do the above you are not actually creating a new array, just grabbing a pointer to that array. You are not copying the actual data.
By keeping your code clean you are losing only the performance of creating memory for a pointer and assigning a pointer a value. So no, you are not losing performance.
The idea of keeping your code clean is much more important than this marginal difference in an extra pointer here and there.
Edit:
I did some testing between the two and as expected, both options perform very similar.
NSMutableArray *data1 = [[NSMutableArray alloc] init];
NSMutableArray *data2 = [[NSMutableArray alloc] init];
NSArray *all = [[NSArray alloc] initWithObjects:data1,data2,nil];
for(int i=0;i<1000;i++)
{
[data1 addObject:[[NSNumber alloc] initWithInt:arc4random()]];
[data2 addObject:[[NSNumber alloc] initWithInt:arc4random()]];
}
double startTime = CACurrentMediaTime();
for(int i=0;i<1000;i++)
{
NSArray *get1 = [all objectAtIndex:0];
NSArray *get2 = [all objectAtIndex:1];
//int get1Index = arc4random() % [get1 count];
//int get2Index = arc4random() % [get2 count];
//NSLog(#"Object at %d: %f", get1Index, [[get1 objectAtIndex:get1Index] doubleValue]);
//NSLog(#"Object at %d: %f", get2Index, [[get2 objectAtIndex:get2Index] doubleValue]);
NSLog(#"Object at %d: %f", i, [[get1 objectAtIndex:i] doubleValue]);
NSLog(#"Object at %d: %f", i, [[get2 objectAtIndex:i] doubleValue]);
}
NSLog(#"Time with temp array:%f", CACurrentMediaTime() - startTime);
startTime = CACurrentMediaTime();
for(int i=0;i<1000;i++)
{
//int get1Index = arc4random() % [[all objectAtIndex:0] count];
//int get2Index = arc4random() % [[all objectAtIndex:1] count];
//NSLog(#"Object at %d: %f", get1Index, [[[all objectAtIndex:0] objectAtIndex:get1Index] doubleValue]);
//NSLog(#"Object at %d: %f", get2Index, [[[all objectAtIndex:1] objectAtIndex:get2Index] doubleValue]);
NSLog(#"Object at %d: %f", i, [[[all objectAtIndex:0] objectAtIndex:i] doubleValue]);
NSLog(#"Object at %d: %f", i, [[[all objectAtIndex:1] objectAtIndex:i] doubleValue]);
}
NSLog(#"Time without temp array:%f", CACurrentMediaTime() - startTime);
//With random access
//2012-01-28 13:44:12.721 test[23164:f803] Time with temp array:0.924193
//2012-01-28 13:44:13.641 test[23164:f803] Time without temp array:0.919250
//2012-01-28 13:44:44.892 test[23191:f803] Time with temp array:0.926337
//2012-01-28 13:44:45.812 test[23191:f803] Time without temp array:0.920447
//With incremental access
//2012-01-28 13:46:43.948 test[23231:f803] Time with temp array:0.935009
//2012-01-28 13:46:44.927 test[23231:f803] Time without temp array:0.978455
//2012-01-28 13:47:40.317 test[23254:f803] Time with temp array:1.173752
//2012-01-28 13:47:41.307 test[23254:f803] Time without temp array:0.989263
The commented out sections are the sections I used for testing random access, for the incremental access I used the current code. Without temp arrays is a fractions quicker, but not noticeably. Not enough to sacrifice readability. I guess that is just the process of writing it out to a variable that slows it down, but, at the same time, having a temp array that is not embedded is much quicker. If you were using the embedded array many times, you would have to do 2 memory accesses instead of 1. So if you are going to use the embedded array several times I imagine the gain would significantly compensate for the loss of using a temp array.