I feel like an idiot not even posting some code, but after reading several articles stating iOS7 Text Kit adds support for Text Folding, I can't actually find any sample code or an attribute to set on the text to fold it and Apple's documentation seems mute on it.
http://asciiwwdc.com/2013/sessions/220 makes me think I set a region of text into its own text container and then display/hide it, perhaps by overriding setTextContainer:forGlyphRange:
Am I anywhere close?
Thanks
There's a WWDC 2013 video that talks a bit about it when they're doing custom text truncation. Basically you implement the NSLayoutManagerDelegate method layoutManager: shouldGenerateGlyphs: properties: characterIndexes: font: forGlyphRange:
It took me way too much struggling to actually come up with code for this, but here's my implementation based on a property hideNotes
-(NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs
properties:(const NSGlyphProperty *)props characterIndexes:(const NSUInteger *)charIndexes
font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange {
if (self.hideNotes) {
NSGlyphProperty *properties = malloc(sizeof(NSGlyphProperty) * glyphRange.length);
for (int i = 0; i < glyphRange.length; i++) {
NSUInteger glyphIndex = glyphRange.location + i;
NSDictionary *charAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:NULL];
if ([[charAttributes objectForKey:CSNoteAttribute] isEqualToNumber:#YES]) {
properties[i] = NSGlyphPropertyNull;
} else {
properties[i] = props[i];
}
}
[layoutManager setGlyphs:glyphs properties:properties characterIndexes:charIndexes font:aFont forGlyphRange:glyphRange];
return glyphRange.length;
}
[layoutManager setGlyphs:glyphs properties:props characterIndexes:charIndexes font:aFont forGlyphRange:glyphRange];
return glyphRange.length;
}
The NSLayoutManager method setGlyphs: properties: characterIndexes: font: forGlyphRange: is called in the default implementation and basically does all of the work. The return value is the number of glyphs to actually generate, returning 0 tells the layout manager to do its default implementation so I just return the length of the glyph range it passes in. The main part of the method goes through all of the characters in the text storage and if it has a certain attribute, sets the associated property to NSGlyphPropertyNull which tells the layout manager to not display it, otherwise it just sets the property to whatever was passed in for it.
Related
I'm working on one app in which the UI rendering will depend upon the JSON sent by the server.
The server will decide UI components, I've actually created extended classes for the basic components like UILabel,UITextField etc, but this seems like very lengthy and complex process.
So now I am looking frameworks which can capable to do it. Since I am going to implement it in other apps too, it needs to be generic. Is there any other way to to do with it?
You can try it out yourself, which would be simpler to implement and debug in case you have any problem. Using any already built framework/library will not provide you flexibility that you MIGHT need.
Consider that you have a function which parses the JSON and decide whether it's textfield/button/label/textview and so on... (It can be one of the attribute in fields array in response).
Make a custom model class say Field, in that class you can have all the details related to the field to be put on the screen; like X, Y, Width, Height, Numeric, Alphanumeric and so on... This all values needs to be parsed from JSON response that you get from API.
You can iterate them one by one like in following function:
- (void)setFieldOnScreen:(Field *)f { // Field is a model class that suits your requirement
if (f.type isEqualToString:#"UITextField"]) {
float x = f.x.floatValue;
float y = f.y.floatValue;
float width = f.width.floatValue;
float height = f.height.floatValue;
UITextField *txtField = [[UITextField alloc] initWithFrame:CGRectMake(x, y, width, height)];
txtField.delegate = self;
txtField.font = f.font_name; // from repsonse
txtField.tag = f.tag.integerValue; // from response
txtField.textAlignment = f.alignment //NSTextAlignmentLeft or whatever from response
// even you can fill up preset values in it from response
txtField.text = f.presetvalue;
... and so on....
} else if ... /// for UILabel/UIButton/UISwitch or any other subclass of the controls
}
}
I have currently subclassed NSLayoutManager along with other classes in the UITextView architecture in order to implement my own Word Processor like UITextView. I am currently facing a problem when trying to layout underlines under ranges of glyphs (to implement something like hyperlink). In order to layout the underlines I have overrode the following method in NSLayoutManager.
- (void)drawUnderlineForGlyphRange:(NSRange)glyphRange underlineType (NSUnderlineStyle)underlineVal baselineOffset:(CGFloat)baselineOffset lineFragmentRect:(CGRect)lineRect lineFragmentGlyphRange:(NSRange)lineGlyphRange containerOrigin:(CGPoint)containerOrigin
In the above method I compute the underline distance in points from origin of the underline to the end of the underline via the glyph range. View the following method:
- (void)getStartLocation:(CGFloat * _Nonnull )startLocation andEndLocation:(CGFloat * _Nonnull)endLocation ofGlyphsInRange:(NSRange)glyphRange {
NSRange characterRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
(*startLocation) = [self locationForGlyphAtIndex:glyphRange.location].x;
CGGlyph glyphs[self.numberOfGlyphs];
NSUInteger numGlyphs = [self getGlyphsInRange:glyphRange glyphs:&glyphs[0] properties:NULL characterIndexes:NULL bidiLevels:NULL];
CTFontRef ctfont = [self getCTFontForGlyphsInRange:glyphRange];
double advances = CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationDefault, &glyphs[0], NULL, numGlyphs);
(*endLocation) = (*startLocation) + (CGFloat)advances;}
As you can see I get the startLocation, and then I get the endLocation by summing the advances for each glyph in the glyphRange. This computation seems to work reasonably well but it seems to result in a overdrawn or underdrawn line for some sequences of characters. It has recently come to my attention that CTFontGetAdvancesForGlyphs does NOT account for font kerning. Can anyone help me with this code? How can I incorporate font kerning effectively into my computation? I have tried querying the NSKernAttribute in my NSTextStorage at the given location but I keep getting back nil values. I have tried both getting the attributeAtIndex and tried enumerating for the attribute over a range.
I am also aware NSAttributedString has underline options but I want to draw the underline myself for various reasons such as supporting custom defined styles, colors, and more.
I'm using iOS charts framework to plot this chart, I want to detect tap or touch only on the line's path or on the small circle's on the lines.
My question is,
Is there any default code block to do this?
I tried comparing the entry.value with the array plotted(as in the following code), but it doesn't workout.
-(void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight *)highlight{
if ([arrayOfPlottedValues containsObject:[NSNumber numberWithInt:(int)entry.value]]) {
//Tapped on line path
}
else{
//Tapped on empty area
}
}
Any insights will be appreciated.
eg : Line chart
I found a way by considering #Wingzero's suggestion, but the major difference was that, I just used the touch point to find out if its on the "marker" or if its outside it. I'm not sure if its the right way, but the solution is,
-(void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight *)highlight{
//-----------------------------------------------------getting recognizer value
UIGestureRecognizer *recognisedGesture = [chartView.gestureRecognizers objectAtIndex:0];
CGPoint poinOfTouch =[recognisedGesture locationInView:chartView];
CGPoint poinOfMarker =[chartView getMarkerPositionWithEntry:entry highlight:highlight];
if (check if the chartview is BarChartView and if true) {
//-----------------------------------------------------If you want to detect touch/tap only on barchartview's bars
if (poinOfTouch.y > poinOfMarker.y) {
NSLog(#"within the bar area!");
}
else{
NSLog(#"Outside the bar area!");
}
}
else
{
//-----------------------------------------------------If you want to detect touch/tap only on linechartView's markers
//-----------------------------------------------------creating two arrays of x and y points(possible nearby points of touch location)
NSMutableArray *containingXValue = [[NSMutableArray alloc]init];
NSMutableArray *containingYValue = [[NSMutableArray alloc]init];
for (int i =0 ; i<5; i++) {
int roundedX = (poinOfMarker.x + 0.5);
int sumXValuesPositive = roundedX+i;
[containingXValue addObject:[NSNumber numberWithInt:sumXValuesPositive]];
int sumXValuesNegative = roundedX-i;
[containingXValue addObject:[NSNumber numberWithInt:sumXValuesNegative]];
int roundedY = (poinOfMarker.y + 0.5);
int sumYValuesPositive = roundedY+i;
[containingYValue addObject:[NSNumber numberWithInt:sumYValuesPositive]];
int sumYValuesNegative = roundedY-i;
[containingYValue addObject:[NSNumber numberWithInt:sumYValuesNegative]];
}
//-----------------------------------------------------------------------------------------------------------------------------------------
int roundXPointTOuched = (poinOf.x + 0.5);
int roundYPointTOuched = (poinOf.y + 0.5);
//-----------------------------------------------------check if touchpoint exists in the arrays of possible points
if ([containingXValue containsObject:[NSNumber numberWithInt:roundXPointTOuched]] && [containingYValue containsObject:[NSNumber numberWithInt:roundYPointTOuched]])
{
// continue, the click is on marker!!!!
}
else
{
// stop, the click is not on marker!!!!
}
//-----------------------------------------------------------------------------------------------------------------------------------------
}
}
}
Edit : The initial solution was applicable only for the line chart, Now if this same situation arises for bar chart, you could handle it with the above code itself.
Man, I'd been running around it for a while now, feeling really great for getting a positive lead. There is no direction for this issue yet, hope this will be useful for someone like me cheers!
P.S. I'm marking this as answer just to make sure, it reaches the needful :). Thanks
It has a default highlight logic, that is, calculate the closest dataSet and xIndex, so we know which data to highlight.
You can customize this logic to restrain the allowed smallest distance. e.g. define the max allowed distance is 10, if the touch point is away from the closest dot > 10, you return false and not highlgiht.
Highlighter is a class, like BarChartHighlighter, ChartHighlighter, etc.
Update towards your comment:
when you tapped, the delegate method get called, so you know which data is highlighted. Your codes seems fine, however the condition code is blackbox to me. But the delegate will be called for sure, so you only have to worry about your logic.
In the iOS Charts library, unlike the BarChartDataSet class, the PieChartDataSet does not contain any property highlightAlpha that can be used to set a different alpha to the selected slice on the pie chart.
Although such a property can be introduced and using CGContextSetAlpha() we can modify the transparency of the highlighted slice, I want to do it without making any change in the library code. How can it be done?
I checked the code, currently, it does not support this.
public override func drawHighlighted(context context: CGContext, indices: [ChartHighlight])
{
...
CGContextSetFillColorWithColor(context, set.colorAt(xIndex).CGColor)
...
}
This just read data set color and use it to highlight. I think you are welcome to file a PR for such feature. Or I will do it when I have time.
For you, changing the source code seems the only option right now. That's why I think it's good for you to contribute.
For now, I have solved the problem using the delegate method:
- (void)chartValueSelected:(ChartViewBase * __nonnull)chartView
entry:(ChartDataEntry * __nonnull)entry
dataSetIndex:(NSInteger)dataSetIndex
highlight:(ChartHighlight * __nonnull)highlight
{
PieChartView *pPieChartView = (PieChartView *)chartView;
PieChartDataSet *pDataSet = (PieChartDataSet *)[pPieChartView.data.dataSets objectAtIndex:dataSetIndex];
NSMutableArray *pColors = [[NSMutableArray alloc] initWithArray:pDataSet.colors
copyItems:YES];
for (int nIndex = 0; nIndex < pColors.count; nIndex++) {
UIColor *pColor = [pColors objectAtIndex:nIndex];
if (nIndex == entry.xIndex) {
pColor = [pColor colorWithAlphaComponent:1];
}
else {
pColor = [pColor colorWithAlphaComponent:0.3];
}
[pColors replaceObjectAtIndex:nIndex
withObject:pColor];
}
pDataSet.colors = pColors;
}
In my case, I load the slices with alpha component less than 1. On highlighting the slice, the alpha value is changed to 1.
The same effect can be achieved if the highlightAlpha property is introduced in PieChartDataSet class. In the drawHighlighted method, CGContextSetAlpha(context, highlightAlpha) needs to be called. The BarChartDataSet has highlight colors as well, which are absent in PieChartDataSet.
Is it possible to retrieve the parameters used to create an SKPhysicsBody object without keeping them around separately? In other words, is there a way to get the body type (i.e. circle, rectangle, polygon) and related information (i.e. radius, size, path) from the following objects after creation:
SKPhysicsBody *circleBody = [SKPhysicsBody bodyWithCircleOfRadius:100.0];
SKPhysicsBody *rectangleBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(100.0, 100.0)];
There don't appear to be any properties or methods in the SKPhysicsNode class reference along the lines of:
SKPhysicsBodyType type = circleBody.bodyType; // Doesn't exist
CGFloat radius = circleBody.radius; // Doesn't exist
I'm surprised that there is information you can pass to a SKPhysicsBody object at creation that isn't at least available through a read-only property later on. Any ideas?
One way you can do this is by analyzing the description string. For example logging a circle body prints this:
<SKPhysicsBody> type:<Circle> representedObject:[(null)]
A regular expression search will do the job, provided that the description string is consistent across shape types and future Sprite Kit versions. Potentially brittle solution, but legal.
The only other way is to use the ObjC runtime to read from properties or ivars. Though this may constitute a use of private APIs and may get the app rejected if you're doing this on a live app.
This code logs all of the private PKPhysicsBody class' properties and ivars.
SKPhysicsBody* circle = [SKPhysicsBody bodyWithCircleOfRadius:10];
NSLog(#"%#", circle);
NSLog(#"%#", NSStringFromClass([circle class])); // log the 'true' class name
unsigned int num;
objc_property_t* properties = class_copyPropertyList(NSClassFromString(#"PKPhysicsBody"), &num);
for (unsigned i = 0; i < num; i++)
{
objc_property_t property = properties[i];
NSLog(#"property: %s", property_getName(property));
}
Ivar* ivars = class_copyIvarList(NSClassFromString(#"PKPhysicsBody"), &num);
for (unsigned i = 0; i < num; i++)
{
Ivar ivar = ivars[i];
NSLog(#"ivar: %s", ivar_getName(ivar));
}
From this it looks like the _shapeType ivar will give you what you're looking for.
I've found a simple way of tracking this information, assuming every body gets bound statically to a single node (as is the case in my application):
SKPhysicsBody *body = [SKPhysicsBody bodyWithCircleOfRadius:10.0];
node.physicsBody = body;
node.userData = [NSMutableDictionary dictionary];
[node.userData setObject:#"circle" forKey:#"shapeType");
[node.userData setObject:[NSNumber numberWithFloat:10.0] forKey:#"radius"];