Keep text in UITextView highlighted when the popover is dismissed - ios

I have a UIPopover that shows up a plain view containing a UITextView filled with some text. I have managed to highlight the text. When the popover is dismissed, and re-opened, the highlight disappears. I want to keep the text highlighted even if if the application is closed. Any ideas how to achieve that?The code i used is the following :
- (void)highlight {
NSRange selectedRange = self.textViewAll.selectedRange;
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithAttributedString:self.textViewAll.attributedText];
[attributedString addAttribute:NSForegroundColorAttributeName
value:[UIColor redColor]
range:selectedRange];
// [highlightedRange addObject:];
// This is where i tried to save each location and length in a mutable array but didn't work
[highlightedRangeLocation insertObject:[NSNumber numberWithInteger:selectedRange.location] atIndex:indexOfHighlight];
[highlightedRangeLength insertObject:[NSNumber numberWithInteger:selectedRange.length] atIndex:indexOfHighlight];
///////////////////////////////////////////////////////////////////////////////
self.textViewAll.attributedText = attributedString;
indexOfHighlight ++ ;
}
- (void)viewDidLoad {
UIMenuItem *highlightMenuItem = [[UIMenuItem alloc] initWithTitle:#"Highlight" action:#selector(highlight)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:highlightMenuItem]];
float sysVer = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVer >= 8.0) {
self.textViewAll.layoutManager.allowsNonContiguousLayout = NO;
}
}
Could anyone point out how to continue from here?
Edit 1 :
The code that close the popover :
- (IBAction)closeFun:(id)sender {
// self.popoverPresentationController set
[self dismissViewControllerAnimated:YES completion:nil];
// [self dismis]
}

Can't you juste save the Highlighted text range in [NSUserDefaults standardUserDefaults] whenever the popover is dismissed, and retrieve it when the popover reappears ?

I think the problem is in the fact that the popover is responsible for the highlighted state, i.e .it is the popover who keeps that fact/state.
The popover is a part of presentation layer / user interface. Surely the highlight represents some fact that ( now comes the catch ) - is completely independent of the popover.
For example highlighting a task could represent that the task is due. Or, highlighting a label to red color could mean that the balance in the bank is in negative numbers.
You see, no matter what user interface element you use, they only represent some underlying business reality.
But what probably happens you create a popover instance, you set it to have a highlighted element. But then this concrete popover instance dies, when it is closed.
And the highlight dies with it.
When you click some button (I guess), a popover shows up, but it is a different instance. This instance doesn't know about highlight.
Even if you somehow managed to keep the one instance of popover alive, and just hide and show it again, the popover should NOT be responsible to know whether something is red or due, (and thus highlighted.)
In you application, you should have a well separated model layer...which is basically a set of related objects that represent state ie. fact that are related to what the application solves from business perspective (for ex. draws lines, calculates interest..stores music..anything really). This model layer, some object in it, should store the facts..ie.e. the task is due, or the balance is low.
Every time you show your popover, you should investigate what are the underlying facts in your model layer right when the popover is being shown. Ivestigating means find a programmatic way to look into model objects, find out about values there and and set up the highlight in that moment again based on this "investigation". You should not rely on the fact that it was highlighted in the not so distant past.

Related

Changing overlaying labels text on a ScrollView

This is sort of hard to explain so I'm including a picture.
So I have a scrollview (the yellow part) and then an overlaying box with score and how many upgrades the player purchased, etc. The green box with the labels stays in the same position while the yellow part scrolls. That works fine, except I am trying to get the labels to change when the player purchases an item. So lets say the player purchases a pickaxe. The upgrades owned should change from "0/20" to "1/20".
This is my setup. I am using SpriteBuilder by the way. I have a class that handles the scrollview, like the buy buttons and descriptions, etc... Then I have another class just for the overlay on the side, and this is basically the only method in it:
- (void)didLoadFromCCB {
if (_doge < 1000000000000) {
balanceLabel.string = [NSString stringWithFormat:#"%.2Lf", _doge];
} else {
balanceLabel.string = #"A lot!";
}
upOwnedLabel.string = [NSString stringWithFormat:#"%d", _upOwned];
upMaxedLabel.string = [NSString stringWithFormat:#"%d", _upMaxed];
NSLog([NSString stringWithFormat:#"%d", _upOwned]);
}
I've tried a few things and this is the one that I think is "most correct". In the didLoadFromCCB on the scroll class, I have this:
UpgradesScene *upgradesScene = [[UpgradesScene alloc] init];
[upgradesScene didLoadFromCCB];
It's in the correct place (under buy button) but for some reason it doesn't update the label. I do get an NSLog message telling me the level, but for some reason the label doesn't work.
I'm very new to this language so please go easy on me :) Thank you
The problem is that you're creating a new upgradesScene with alloc init rather than getting a reference to the one that's in your scroll view. You should have a property (or IBOutlet if you're making this in IB) that points to that view.

Why is textView:shouldInteractWithTextAttachment:inRange: never getting called on UITextView delegate?

Is anyone using this delegate method ? I get callbacks on
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
But not on this one. The documentation seems a bit ambiguous about what this is intended for
- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange
According to the documentation on the Web this is what its intended for:
Discussion
The text view calls this method if the user taps or long-presses the text attachment and its image property is not nil. Implementation of this method is optional. You can use this method to trigger an action in addition to displaying the text attachment inline with the text.
And here is Xcode 5 documentation:
Asks the delegate if the specified text view should display the provided text attachment in the given range of text.
The text view calls this method when a text attachment character is recognized in its text container by a data detector. Implementation of this method is optional. You can use this method to trigger an alternative action besides displaying the text attachment inline with the text in the given range.
EDIT:
Mmm OK I figured out the problem. If I paste an image in from iOS then it works, however if the image was pasted in from OS X it does not. It seems that the actual attachment formats used are not quite the same on both platforms despite the fact that the image appears to show up correctly in the text views. On closer inspection the NSTextAttachment classes don't appear to be the same on iOS as on OS X.
If anyone can shed any light on the cross platform compatibility here please do.
Also if I save the attributed string after pasting the image in on iOS and then retrieve it and display it in the UITextView interaction with the attachment is no longer possible. It would appear that when storing the image the image is actually placed in contents if contents is nil. So maybe I am going to have to iterate through all attachments to check what data is stored where particularly to figure out any differences in behaviour across the OS X and iOS platforms.
FURTHER EDIT:
The method only gets called if the attachment image is NOT nil and despite the fact that an image is displayed the actual image attribute can actually be nil, silly me! Anyway the fix seems to be to check all the attachments in the attributed string and to set their image attribute to something, usually the contents of the fileWrapper. The default NSTextAttachment behaviour seems to be to store the image in the fileWrapper when its archived but it does not do the reverse when its unarchived. Anyway I want to retain the original image in the attachment but depending on the device display a suitably scaled version of the original !
The chief thing is that the text view's editable property must be NO and it's selectable property must be YES. Those are the only circumstances under which this delegate method is called. If you are getting shouldBeginEditing then your text field is editable which is exactly what it must not be.
Here is what I do to ensure the NSTextAttachments image attribute gets set when restoring the UITextView's attributed string from archived data (in this case whenever the user selects a record from a Core Data store).
I set the UITextView up as a delegate for textStorage and in the didProcessEditing look for any attachments that may have been added and then check that their image attribute is set. I am also setting the scaling factor on the image to make sure the image scales appropriately for the device.
This way I don't loose the original resolution of the image and if the user wants to view it in more detail I provide the option to open it in an image browser window from a popup menu.
Hope this helps someone else.
EDIT:
Check here for more details on NSTextView and UITextView http://ossh.com.au/design-and-technology/software-development/
- (void)textStorage:(NSTextStorage *)textStorage didProcessEditing:(NSTextStorageEditActions)editedMask range:(NSRange)editedRange changeInLength:(NSInteger)delta {
//FLOG(#"textStorage:didProcessEditing:range:changeInLength: called");
[textStorage enumerateAttributesInRange:editedRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:
^(NSDictionary *attributes, NSRange range, BOOL *stop) {
// Iterate over each attribute and look for a Font Size
[attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([[key description] isEqualToString:NSAttachmentAttributeName]) {
NSTextAttachment *attachment = obj;
//Reset the image attribute and scale for the device size if necessary
[self resetAttachmentImage:attachment];
}
}];
}];
}
- (void)resetAttachmentImage:(NSTextAttachment*)attachment {
UIImage *image = [attachment image];
float factor = 2;
if (image == nil) {
if (attachment.fileWrapper == nil || !attachment.fileWrapper.isRegularFile) {
attachment.image = [UIImage imageNamed:#"unknown_attachment.png"];
return;
}
//Usually retrieved from store
image = [UIImage imageWithData:attachment.fileWrapper.regularFileContents];
} else {
// Convert any pasted image
image = [UIImage imageWithData:UIImagePNGRepresentation(image)];
}
float imgWidth = image.size.width;
// If its wider than the view scale it to fit
if (imgWidth > _viewWidth) {
factor = imgWidth / _viewWidth + 0.5;
attachment.image = [UIImage imageWithData:UIImagePNGRepresentation(image) scale:factor];
} else {
attachment.image = [UIImage imageWithData:UIImagePNGRepresentation(image) scale:_scale];
}
}

Data from DetailViewController not displaying

I'm making a chemistry calculator segment in an app I'm working on, and I cannot get the data and I cannot get the information to correctly populate the screen. There is a secondary issue, the alignment, and if someone can help me with that I'd greatly appreciate it, but it isn't the primary focus of this question - I'll make a dedicated topic for it.
So I'll go over what I want to do, and what I've tried. What I'm trying to do is make a chemistry calculator where depending on what equation is selected, a UIStepper max/min.Value is modified to include all possible derivations of that equation, as well as certain UILabels and UITextFields shown/hidden.
I have confirmed that I have data passed down from the MasterViewController as I've set the data to an NSString called _equation, and successfully used _equation to modify the title of the DetailViewController under self.title in viewDidLoad.
I have tried placing and initializing all UIStepper properties appropriately under a if/if else nest under viewDidLoad (which also quantizes the _equationName possible values to an integer (eqNum) so that it can be used in a switch statement). I have also tried placing the UITextField hidden properties under viewDidLoad, to no avail.
So without further ado, let's get to the code. I've truncated the code down to one equation so you can see what's going on here easier - note that this is nested under the IBAction for the Calculate button:
// Take _equationName quantization and use it in a switch case to determine the formula that IBAction will use:
if (dflt)
{
switch (eqNum)
{
case 1:
if ((stepper.value = 1))
{
// Change deriv_units appropriately:
deriv_units.text = #"Energy (Joules)";
// This is a Planck's constant calculation, we hide the second variable as the constant
// is stored:
value2.hidden = YES;
value2_type.hidden = YES;
// Now we set up the parameters of the first entry variable:
value1_type.text = #"Frequency (in Hz)";
double frequency = [value1.text doubleValue];
double Planck = 6.626069e-34;
double energy = Planck * frequency;
// Now we set up the return field to return results:
NSString* resultIntermediate = [NSString stringWithFormat:#"%f", energy];
result.text = resultIntermediate;
units.text = #"J";
}
and the subsequent code under viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
self.title = _equationName;
int eqNum;
if ((_equationName = #"Energy-Frequency Relation"))
{
eqNum = 1;
// Set stepper bounds for derivation:
[stepper setMinimumValue:1];
[stepper setMaximumValue:3];
self.stepper.stepValue = 1;
self.stepper.wraps = NO;
self.stepper.autorepeat = NO;
self.stepper.continuous = YES;
// This is primarily a two-variable equation, so hide UITextView and UILabel #3:
value3.hidden = YES;
value3_type.hidden = YES;
}
(Props to anyone who recognizes this - it's Planck's relation! :D)
Here is what the GUI is supposed to look like (as it appears in Storyboard):
Here is what it comes out looking like in the iOS Simulator:
Note the misalignment issue, which isn't the principle issue in play here.
Also note that right now, the switch statement for equation parameters is under an if tree that checks to see if dflt (a Boolean variable assigned to UISwitch) returns true for double-precision calculations. However, upon toggling the switch ON, the issue does not correct.
Here's an even more complete explanation:
value#.text is the number entered in one of the three UITextFields, from top to bottom.
value#_type is the text to be displayed in the corresponding UILabel.
deriv_units is the UILabel below the one marked "Derivation Units", and is used to display which derivation of the equation has been selected using the UIStepper.
At bottom: The rightmost UILabel is the result label, whereas the leftmost is the units label.
Many thanks to anyone who can help this beginning developer along the path of programming righteousness.
About your alignment issue: it looks as though you are creating the storyboard for 4" screen, while running it on a 3.5" screen. In the storyboard onnthe lower right there are some small buttons, one of thise allows you to change instantly between the display for either 4" or 3.5".
Have you made sure your controls are connected to your code?
- (void) viewDidAppear: (BOOL) animated{
[super viewDidAppear: animated];
// some rude NSAsserts, but let's be sure
NSAssert(value1_type != nil, #"Your control element is nil, it might not be hooked up");
// you should now see this text in the textfield
value1_type.text = #"Frequency (in Hz)";
NSAssert(result != nil, #"Your control element is nil, it might not be hooked up");
result.text = #"some test text";
NSAssert(units != nil, #"Your control element is nil, it might not be hooked up");
units.text = #"J";
}

Continuously Update Label Based on UISlider Value

I currently have a series of 5 UISliders that continuously update their respective labels to display what value the user is selecting in an hh:mm:ss format. I have the properties of each slider set to continuous and have their targets set to when the slider is changed (This example would be for just the first slider).
sliderOne.continuous=YES;
[sliderOne addTarget:self action:#selector(sliderOneChanged:) forControlEvents:UIControlEventTouchUpInside];
Each slider's individual label updates the time selected perfectly. Now however, I need to update another label underneath all of these sliders that continuously sums up the value of the sliders.
At the same time, I am also doing some conversions with the value of the slider using two segmented controllers -- a miles/kilometers segmented controller and various distances in another segmented controller. Each conversion results in a time in seconds that is then converted back into the hh:mm:ss format and displayed in the last label when the user selects which combo from the segment controller they want. I have all the math worked out for this, and the label will update correctly when changing segments, but not continuously.
How could I update a label to continuously show the sum of every UISlider as the user changes them?
For a continuous updating, use the UIControlEventValueChanged event along with the continuous property
sliderOne.continuous=YES;
[sliderOne addTarget:self action:#selector(sliderOneChanged:) forControlEvents:UIControlEventValueChanged];
Use KVO to get notified of changes in UISliders and update the UI accordingly. You want to wrap UI updates with dispatch_async to avoid locking the interface.
Using KVO-Notification-Manager it can be something like this (in viewDidLoad or similar):
id token1 = [slider1 addKVOBlockForKeyPath:#"value" options:NSKeyValueObservingOptionNew handler:^(NSString *keyPath, id object, NSDictionary *change) {
dispatch_async(dispatch_get_main_queue(), ^{
whateverSliderNeedsUpdate.value = /* your calculation result */;
});
}];
id token2 = [slider2 /* repeat for as many sliders as you want */];
...
It's important to remove the observers at some point.
- (void)dealloc {
[slider1 removeKVOBlockForToken:token1];
[slider2 ...];
}
If you feel adventurous you can try with ReactiveCocoa.
You could create a method that does all of your math and updates your UILabel with the result. For example:
- (void)updateSumLabel {
// this is obviously very simple
float num1 = [_sliderOne value];
float num2 = [_sliderTwo value];
float sum = num1 + num2;
[_sumLabel setText:[NSString stringWithFormat:#"#f",sum]];
}
The you can call updateSumLabel from any of your existing methods like sliderOneChanged. This is probably the most simple way todo this.

Stuck with UIActionSheetPicker , iOS

This is going to be a very targeted question , as its for people that have actually used the UIActionSheetPicker before.
I used it in my iphone applications and everything worked great , but now that i tried to implement it on my ipad i experienced some problems.
The main problem is that on ipad the picker appears as a "bubble" or "info window" if you prefer , which points at the spot where it was called , like a button a text field etc.
Below is an example of it :
You can see that the "info window" is pointing at the animals button.
In my application i have 4 different buttons. 2 are in the top side of the screen and 2 at the bottom.
Those who are at the bottom , call the picker very well and the picker appears on top of the buttons pointing down on them.
However , the ones on the top of the screen (almost like the one in the image) when they call the picker the picker appears much lower on the screen and doesnt point at them as it should ...
I mean i expected to appear just under the buttons pointing at them (like in the image), but they are almost in the center of the screen pointing nowhere..
Any ideas? Has someone experienced this before?
EDIT
In the beginning , i was calling the ActionSheetPicker inside the buttons action like this:
- (IBAction)selectTopic:(UIControl *)sender {
[ActionSheetStringPicker showPickerWithTitle:NSLocalizedString(#"CHOOSE_TOPIC", nil) rows:self.topics initialSelection:self.selectedIndex target:self successAction:#selector(topicWasSelected:element:) cancelAction:#selector(actionPickerCancelled:) origin:sender];
//when user picks a new topic we clean the titleField to make sure selected title of previous topic doesnt mix up with new topic
titleField.text = #"";
}
Now i am trying to call it like this:
- (IBAction)selectTopic:(UIControl *)sender {
ActionSheetStringPicker *thePicker = [[ActionSheetStringPicker alloc] initWithTitle:NSLocalizedString(#"CHOOSE_TOPIC", nil) rows:self.topics initialSelection:self.selectedIndex target:self successAction:#selector(topicWasSelected:element:) cancelAction:#selector(actionPickerCancelled:) origin:sender];
thePicker.presentFromRect = topicField.frame;
[thePicker showActionSheetPicker];
//when user picks a new topic we clean the titleField to make sure selected title of previous topic doesnt mix up with new topic
titleField.text = #"";
}
Where topicField is my TextField.
Sadly the result is the same. Even now that i am specifying where i want the arrow to point , the picker is called 300 pixels down.
The strange thing is though that even with another other button that is a bit lower than the previous the picker is again exactly 300 pixels down.
EDIT2
After noticing that the picker shows 300 pixels down , i decided to manually make it show 300pixels up , to point exactly on my button.
I used the following code :
- (IBAction)selectTopic:(UIControl *)sender {
//[ActionSheetStringPicker showPickerWithTitle:NSLocalizedString(#"CHOOSE_TOPIC", nil) rows:self.topics initialSelection:self.selectedIndex target:self successAction:#selector(topicWasSelected:element:) cancelAction:#selector(actionPickerCancelled:) origin:sender];
ActionSheetStringPicker *thePicker = [[ActionSheetStringPicker alloc] initWithTitle:NSLocalizedString(#"CHOOSE_TOPIC", nil) rows:self.topics initialSelection:self.selectedIndex target:self successAction:#selector(topicWasSelected:element:) cancelAction:#selector(actionPickerCancelled:) origin:sender];
CGRect pickerFrame = topicField.frame;
pickerFrame.size.height -= 300;
thePicker.presentFromRect = pickerFrame;
[thePicker showActionSheetPicker];
//when user picks a new topic we clean the titleField to make sure selected title of previous topic doesnt mix up with new topic
titleField.text = #"";
}
Amazingly the button once again appears in the same position 300pixels down. I start to believe that this one may not be the property to alter the position of the picker.
Any ideas ?
Setting the presentFromRect in your calling code won't make a difference since it will be reset based on the sender within the ActionSheetPicker code, instead you are going to need to modify the source code of the library.
The following (unmerged) commit on github should resolve the issue On iPad, set the popover to point properly at its container.
Basically within the AbstractActionSheetPicker.m file modify the - (void)presentPickerForView:(UIView *)aView method like the following
- (void)presentPickerForView:(UIView *)aView {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
self.presentFromRect = CGRectMake(0, 0, self.containerView.frame.size.width, self.containerView.frame.size.height);
[self configureAndPresentPopoverForView:aView];
}
else {
self.presentFromRect = aView.frame;
[self configureAndPresentActionSheetForView:aView];
}
}
After a brief look at the code, I would say that you want to make sure that the presentFromRect on the ActionPicker object is in the coordinates of the container that you are passing in on the init. For instance if I had one big view that contained a button:
This is untested:
UIView* someBigView = ... // get the big view
UIButton* someButton = ... // get the button that the big view contains
ActionSheetDatePicker* thePicker = [[ActionSheetDatePicker alloc] initWithTitle:#"Some Title"
datePickerMode:UIDatePickerModeDate
selectedDate:[NSDate date]
target:self
action:#selector(someMethod:)
origin:someBigView];
//the arrow should be pointing at the button
thePicker.presentFromRect = someButton.frame;
[thePicker showActionSheetPicker];
In my opinion, this library has a pretty big flaw in the fact that it takes an origin parameter that can be a button item or a container. It requires that you look at the source to understand it, instead of being (somewhat) obvious from the interface.

Resources