Modify UIStepper autorepeat behaviour - ios

I have a client who wants me to modify a UIStepper's autorepeat behaviour.
If the user selects touches and holds say the + button of the stepper, the value of the stepper starts increasing at a rate of 1 every half second or so, and then after about 3 seconds, the values start changing much more rapidly.
Is there anyway to modify how this works, so that, for example, the values will increase at the faster rate immediately if the user taps and holds?
I've looked at the UIStepped documentation and I didn't see anything regarding this, but I wondered if there was a way to do this through an IBAction or something.

First, add two actions for your stepper:
[theStepper addTarget:self action:#selector(stepperTapped:) forControlEvents:UIControlEventTouchDown];
[theStepper addTarget:self action:#selector(stepperValueChanged:) forControlEvents:UIControlEventValueChanged];
Here's what those actions look like:
- (IBAction)stepperTapped:(id)sender {
self.myStepper.stepValue = 1;
self.myStartTime = CFAbsoluteTimeGetCurrent();
}
- (IBAction)stepperValueChanged:(id)sender {
self.myStepper.stepValue = [self stepValueForTimeSince:self.myStepperStartTime];
// handle the value change here
}
Here's the magic code:
- (double)stepValueForTimeSince:(CFAbsoluteTime)aStartTime {
double theStepValue = 1;
CFAbsoluteTime theElapsedTime = CFAbsoluteTimeGetCurrent() - aStartTime;
if (theElapsedTime > 6.0) {
theStepValue = 1000;
} else if (theElapsedTime > 4.0) {
theStepValue = 100;
} else if (theElapsedTime > 2.0) {
theStepValue = 10;
}
return theStepValue;
}

It seems that it is not possible to modify this behaviour, and using custom UIButtons is the best solution.

Related

Calling an IBAction from another method

I am new to Objective-C, Xcode and all of the good stuff. I am self teaching.
I am trying to call an IBAction from another method.
- (IBAction)strAdj:(UIStepper *)strStepper
{
// Converts stepper to integer
NSUInteger strvalue = strStepper.value;
// Changes the the text to the value of the stepper
strStat.text = [NSString stringWithFormat:#"%2d", (unsigned)strvalue];
_strMod.text = [NSString stringWithFormat:#"%2d", (unsigned)stepperAdj(strvalue)];
// Based on the value it change the strMod to a specific value
}
I am only posting a portion of the next code. It is a simple switch statement. I basically want to call the IBAction above in the Void below.
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
switch (row) {
case 0:
break;
// Core races never change
// defines dwarf stats
case 1:
// sets all the increases
tempCon = 2;
tempWis = 2;
tempChr = -2;
// resets all other stats
tempStr = 0;
tempDex = 0;
tempInt = 0;
// I want to call the IBAction here...
break;
This IBAction will need to occur ultimately 15 times. The above switch statement occurs when the picker is changed. Where as the IBAction happens every time the stepper occurs.
I hope that I am not too far from what I want to do. Again I have only been working with this for the last several days and I have not been able to find what I am looking for or if I did then I wasn't sure what to do.
It kinda looks like you have a dropdown for the player to select a race and each attribute (str, con, wis, etc.) has a stepper to bump the respective values up or down.
I don't know enough to speculate, but I suspect there is a better approach overall. That said, I think a brute force solution to what you are trying to do is to make sure each of your steppers has a referencing outlet: stepperWis, stepperCon, stepperInt, etc. Then in your switch you can do something like so (for each attribute):
stepperWis.value = (whatever value you want);
[self strAdj:self.stepperWis];
You can call UIButton TouchUpInside event programatically like :
[YourButtonObject sendActionsForControlEvents: UIControlEventTouchUpInside];

Is there a public way to force MPNowPlayingInfoCenter to show podcast controls?

I would like Control Center (via MPNowPlayingInfoCenter) to show the forward 15 seconds / back 15 seconds controls that Apple shows with podcasts, like so:
The utter lack of documentation tells me that there's no obvious way to do this, but has anyone out there found any non-obvious way to force this without resorting to a private method?
I've already got my handling for the forward/back button set up to advance appropriately, I'd just like to use the more appropriate UI. Any help would be greatly appreciated.
OK so I had a bit of time on my hands and so I followed the breadcrumb.…
This is what I found.
Include the MediaPlayer framework and get hold of the RemoteCommandCenter:
MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
then if you wanted to set the skip controls as per Overcast you can do the following:
MPSkipIntervalCommand *skipBackwardIntervalCommand = [rcc skipBackwardCommand];
[skipBackwardIntervalCommand setEnabled:YES];
[skipBackwardIntervalCommand addTarget:self action:#selector(skipBackwardEvent:)];
skipBackwardIntervalCommand.preferredIntervals = #[#(42)]; // Set your own interval
MPSkipIntervalCommand *skipForwardIntervalCommand = [rcc skipForwardCommand];
skipForwardIntervalCommand.preferredIntervals = #[#(42)]; // Max 99
[skipForwardIntervalCommand setEnabled:YES];
[skipForwardIntervalCommand addTarget:self action:#selector(skipForwardEvent:)];
and in the event handlers do what you need to do to skip by the interval:
-(void)skipBackwardEvent: (MPSkipIntervalCommandEvent *)skipEvent
{
NSLog(#"Skip backward by %f", skipEvent.interval);
}
-(void)skipForwardEvent: (MPSkipIntervalCommandEvent *)skipEvent
{
NSLog(#"Skip forward by %f", skipEvent.interval);
}
Note: The preferredIntervals property is an NSArray but I haven’t figured out how extra intervals can be utilised by the command center unless you do something with this yourself.
Things to note that I’ve found so far. When you do this you are taking control of all the controls so the default play and pause buttons won't show unless you do the same for them:
MPRemoteCommand *pauseCommand = [rcc pauseCommand];
[pauseCommand setEnabled:YES];
[pauseCommand addTarget:self action:#selector(playOrPauseEvent:)];
//
MPRemoteCommand *playCommand = [rcc playCommand];
[playCommand setEnabled:YES];
[playCommand addTarget:self action:#selector(playOrPauseEvent:)];
(there is also a togglePlayPauseCommand defined but I could’t get this to fire from the Command Centre - it does fire from headphones though.)
Other discoveries:
The buttons are in fixed positions left / middle / right so you cant have (for example) a previousTrack and a skipBackward as they both occupy the left position.
There are seekForward / seekBackward commands that need a prevTrack and nextTrack command to be triggered. When you set up both then a single tap triggers next / previous and a press and hold triggers a begin seek and an end seek when you lift your finger.
// Doesn’t show unless prevTrack is enabled
MPRemoteCommand *seekBackwardCommand = [rcc seekBackwardCommand];
[seekBackwardCommand setEnabled:YES];
[seekBackwardCommand addTarget:self action:#selector(seekEvent:)];
// Doesn’t show unless nextTrack is enabled
MPRemoteCommand *seekForwardCommand = [rcc seekForwardCommand];
[seekForwardCommand setEnabled:YES];
[seekForwardCommand addTarget:self action:#selector(seekEvent:)];
-(void) seekEvent: (MPSeekCommandEvent *) seekEvent
{
if (seekEvent.type == MPSeekCommandEventTypeBeginSeeking) {
NSLog(#"Begin Seeking");
}
if (seekEvent.type == MPSeekCommandEventTypeEndSeeking) {
NSLog(#"End Seeking");
}
}
There is also a feedback mechanism that I haven’t seen before (occupies left position)
MPFeedbackCommand *likeCommand = [rcc likeCommand];
[likeCommand setEnabled:YES];
[likeCommand setLocalizedTitle:#"I love it"]; // can leave this out for default
[likeCommand addTarget:self action:#selector(likeEvent:)];
MPFeedbackCommand *dislikeCommand = [rcc dislikeCommand];
[dislikeCommand setEnabled:YES];
[dislikeCommand setActive:YES];
[dislikeCommand setLocalizedTitle:#"I hate it"]; // can leave this out for default
[dislikeCommand addTarget:self action:#selector(dislikeEvent:)];
BOOL userPreviouslyIndicatedThatTheyDislikedThisItemAndIStoredThat = YES;
if (userPreviouslyIndicatedThatTheyDislikedThisItemAndIStoredThat) {
[dislikeCommand setActive:YES];
}
MPFeedbackCommand *bookmarkCommand = [rcc bookmarkCommand];
[bookmarkCommand setEnabled:YES];
[bookmarkCommand addTarget:self action:#selector(bookmarkEvent:)];
// Feedback events also have a "negative" property but Command Center always returns not negative
-(void)dislikeEvent: (MPFeedbackCommandEvent *)feedbackEvent
{
NSLog(#"Mark the item disliked");
}
-(void)likeEvent: (MPFeedbackCommandEvent *)feedbackEvent
{
NSLog(#"Mark the item liked");
}
-(void)bookmarkEvent: (MPFeedbackCommandEvent *)feedbackEvent
{
NSLog(#"Bookmark the item or playback position");
}
This displays three horizontal bars and brings up an alert sheet - you can highlight these individually by setting the active property.
There is also a rating command defined - but I couldn't get this to show in the Command Center
// MPRatingCommand *ratingCommand = [rcc ratingCommand];
// [ratingCommand setEnabled:YES];
// [ratingCommand setMinimumRating:0.0];
// [ratingCommand setMaximumRating:5.0];
// [ratingCommand addTarget:self action:#selector(ratingEvent:)];
and a playback rate change command - but again couldn’t get this to show in Command Center
// MPChangePlaybackRateCommand *playBackRateCommand = [rcc changePlaybackRateCommand];
// [playBackRateCommand setEnabled:YES];
// [playBackRateCommand setSupportedPlaybackRates:#[#(1),#(1.5),#(2)]];
// [playBackRateCommand addTarget:self action:#selector(remoteControlReceivedWithEvent:)];
There is also a block-based target action mechanism if you prefer
// #property (strong, nonatomic) id likeHandler;
self.likeHandler = [likeCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *event) {
NSLog(#"They like it");
return MPRemoteCommandHandlerStatusSuccess; // or fail or no such content
}];
One final point to be aware of: If you have registered to receive remote events via [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; then some of these commands also trigger events in the - (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent handler. These are UIEvents though with type UIEventTypeRemoteControl and a subtype to distinguish the event. You can't mix and match these with MPRemoteCommandEvents in this method. There are hints that MPRemoteCommandEvents will replace the UIEvents at some point.
All of this based on trial and error so feel free to correct.
Gareth
For Swift developers
import MediaPlayer
let rcc = MPRemoteCommandCenter.shared()
let skipBackwardCommand = rcc.skipBackwardCommand
skipBackwardCommand.isEnabled = true
skipBackwardCommand.addTarget(handler: skipBackward)
skipBackwardCommand.preferredIntervals = [42]
let skipForwardCommand = rcc.skipForwardCommand
skipForwardCommand.isEnabled = true
skipForwardCommand.addTarget(handler: skipForward)
skipForwardCommand.preferredIntervals = [42]
func skipBackward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
let interval = command.preferredIntervals[0]
print(interval) //Output: 42
return .success
}
func skipForward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let command = event.command as? MPSkipIntervalCommand else {
return .noSuchContent
}
let interval = command.preferredIntervals[0]
print(interval) //Output: 42
return .success
}
Other command would be the similar and they can be checked here
Oooooooh. Marco Arment got this to work in Overcast, and at least left a breadcrumb trail for the Castro guys with this tweet:
It’s MPRemoteCommandCenter. Good luck with the documentation, though.
Here's said documentation for anyone who's been following this question - I'm guessing it has to do with skipBackwardCommand and skipForwardCommand. I don't have time to look into it this very second, so I'll leave this here in case anyone wants to poke at it and give a more thorough answer.
Apple has no documentation because there is no way to change this. Again, Apple is keeping the best stuff to itself (Siri comes to mind also).
The jailbroken version supports changing Control Center buttons, which I found on this site. I have a feeling that you want to use this app on the actual iOS 7, not a jailbroken version, so this does not help you at all.
These private API's get in the way of developing good apps way too often. Unless Apple gives us more freedom to use currently-private API's, you are out of luck.
In addition to the other answers, I found if I did not set the nowPlayingInfo on MPNowPlayingInfoCenter then the skip buttons did not appear but the default nextTrack and PreviousTrack buttons appeared. (plain fastforward and rewind appearing buttons)
Be sure you are setting MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo at some point like below:
var songInfo: NSMutableDictionary = [
MPMediaItemPropertyTitle: "song title",
MPMediaItemPropertyArtist: "artist ",
MPNowPlayingInfoPropertyElapsedPlaybackTime: "0"
]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = songInfo as [NSObject : AnyObject]

iOS: Moving a uibutton by its tag outside of where it was generated

Is it possible? I have an array of buttons created in an void and I want to move one when a neighbouring one is called in the buttonTapped void. I have no trouble moving the button thats pressed because it is the sender, but I also need to move the one next to it and can't seem to get it to move. Each button in the array has a tag value so they're unique.
Thanks
Reasonable intuitive assumption: you generated the buttons so that they are ordered in the array...
- (void)moveButton:(UIButton *)sender // whatever
{
NSUInteger idx = [buttonArray indexOfObject:sender] + 1;
UIButton *nextButton = idx < buttonArray.count ? [buttonArray objectAtIndex:idx] : nil;
// do something with `nextButton`
}

How to access multiple buttons in access function?

I have two buttons in each row of a tableview. One is labeled "have it" the other "want it" Each button starts off at 20% opacity when the app starts. When one button is tapped the opacity is set to 100% . I need logic so that if one button is set to 100% opacity and the other one set at 20% is tapped, the first button needs to be set to 20% and the second button to 100% (so the opacity needs to be reversed).
Each button has it's own action that is run when pressed. I can access the button that is pressed and set the opacity with (UIButton *senderButton = (UIButton *)sender). However I need to set the opacity of the other button as well. How can access the other button (the one that was not pressed) inside of my action/function that is called when one is pressed? Thanks!
You can create an outlet for each button. So that you can set its property from any where within its container class.
if I correct understand your question, you can declare your buttons in header-file like this:
#interface myController : UIViewController
{
UIButton *b1;
UIButton *b2;
}
tmen in m-file (in viewDidLoad) you can set this buttons with one selector and different tags: (for more information about creation buttons: How do I create a basic UIButton programmatically?)
-(void)viewDidLoad
{
[super viewDidLoad];
b1 = [UIButton buttonwithType:UIButtonTypeCustom];
[b1 addTarget:self withAction:#selector(clickINMyButtons:) forState:UIControlTouchUPInside]; // sorry, I don't remember correct syntax, i'll correct this some later if you needed in it.
b1.tag = 1;
b1.frame = CGRectMake(0,0,12,12); //example
[self.view addSubView:b1];
}
alike declare b2 with different:
b2.tag = 2;
So, then you implement your selector with changing opacity:
-(void)clickINMyButtons:(UIButton *)sender
{
if (sender.tag == 1)
{
sender.alpha = 1; // or b1.alpha = 1;
b2.alpha = 0.2;
}
else if (sender.tag == 2)
{
sender.alpha = 1; // or b2.alpha = 1;
b1.alpha = 0.2;
}
}

UISlider value changed update result

I have three labels receiving output from three UISliders. A result is calculated and placed in a fourth label when a segmented control is touched.
How do I make the result update if one of the slider values is changed without touching the segmented control again?
First, add an event for your slider changing values:
[mySlider addTarget:self
action:#selector(sliderValueChanged:)
forControlEvents:UIControlEventValueChanged];
You will want to do this for each slider.
In your sliderValueChanged: function:
- (void)sliderValueChanged:(id)sender {
[self calculateResult];
}
In calculateResult you can do your calculations and set your result box.
If you need to make sure your segmented control is in some state first, just add an if clause to sliderValueChanged:
- (void)sliderValueChanged:(id)sender {
if ([mySegmentedControl selectedSegmentIndex] == 1) {
[self calculateResult];
}
}
I came up with something that worked using mopsled's answer:
if (UIControlEventValueChanged && mySeg.selectedSegmentIndex == 0) {
float floatResult = [f3.text floatValue]-[f2.text floatValue];
float coldFactor = [fcold.text floatValue]*floatResult;
float result = [f1.text floatValue]+coldFactor;
NSString *tpResult = [[NSString alloc] initWithFormat:#"%.0f",result];
f4Result.text = tpResult;
}

Resources