How can i convert NSBezierPath to CGPath - cocoa

How can i convert between NSBezierPath to CGPath.
Thanks.

Right from Apple documentation: Creating a CGPathRef From an NSBezierPath Object
Here is the relevant code.
#implementation NSBezierPath (BezierPathQuartzUtilities)
// This method works only in OS X v10.2 and later.
- (CGPathRef)quartzPath
{
int i, numElements;
// Need to begin a path here.
CGPathRef immutablePath = NULL;
// Then draw the path elements.
numElements = [self elementCount];
if (numElements > 0)
{
CGMutablePathRef path = CGPathCreateMutable();
NSPoint points[3];
BOOL didClosePath = YES;
for (i = 0; i < numElements; i++)
{
switch ([self elementAtIndex:i associatedPoints:points])
{
case NSMoveToBezierPathElement:
CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
break;
case NSLineToBezierPathElement:
CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
didClosePath = NO;
break;
case NSCurveToBezierPathElement:
CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
points[1].x, points[1].y,
points[2].x, points[2].y);
didClosePath = NO;
break;
case NSClosePathBezierPathElement:
CGPathCloseSubpath(path);
didClosePath = YES;
break;
}
}
// Be sure the path is closed or Quartz may not do valid hit detection.
if (!didClosePath)
CGPathCloseSubpath(path);
immutablePath = CGPathCreateCopy(path);
CGPathRelease(path);
}
return immutablePath;
}
#end
Bug Reporter
rdar://15758302: NSBezierPath to CGPath.

The syntax in Xcode 8 GM has been further simplified, code modified from rob-mayoff's answer above. Using this and a helper for addLine(to point: CGPoint) I am sharing drawing code cross platform.
extension NSBezierPath {
public var cgPath: CGPath {
let path = CGMutablePath()
var points = [CGPoint](repeating: .zero, count: 3)
for i in 0 ..< elementCount {
let type = element(at: i, associatedPoints: &points)
switch type {
case .moveTo:
path.move(to: points[0])
case .lineTo:
path.addLine(to: points[0])
case .curveTo:
path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePath:
path.closeSubpath()
#unknown default:
continue
}
}
return path
}
}

This works in Swift 3.1 and later:
import AppKit
public extension NSBezierPath {
public var cgPath: CGPath {
let path = CGMutablePath()
var points = [CGPoint](repeating: .zero, count: 3)
for i in 0 ..< self.elementCount {
let type = self.element(at: i, associatedPoints: &points)
switch type {
case .moveToBezierPathElement: path.move(to: points[0])
case .lineToBezierPathElement: path.addLine(to: points[0])
case .curveToBezierPathElement: path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePathBezierPathElement: path.closeSubpath()
}
}
return path
}
}

For macOS better use - CGMutablePath
But, if you want cgPath for NSBezierPath:
Swift 5.0
extension NSBezierPath {
var cgPath: CGPath {
let path = CGMutablePath()
var points = [CGPoint](repeating: .zero, count: 3)
for i in 0 ..< self.elementCount {
let type = self.element(at: i, associatedPoints: &points)
switch type {
case .moveTo:
path.move(to: points[0])
case .lineTo:
path.addLine(to: points[0])
case .curveTo:
path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePath:
path.closeSubpath()
#unknown default:
break
}
}
return path
}
}

Here is a Swift version if anyone else finds a need for it:
extension IXBezierPath {
// Adapted from : https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html#//apple_ref/doc/uid/TP40003290-CH206-SW2
// See also: http://www.dreamincode.net/forums/topic/370959-nsbezierpath-to-cgpathref-in-swift/
func CGPath(forceClose forceClose:Bool) -> CGPathRef? {
var cgPath:CGPathRef? = nil
let numElements = self.elementCount
if numElements > 0 {
let newPath = CGPathCreateMutable()
let points = NSPointArray.alloc(3)
var bDidClosePath:Bool = true
for i in 0 ..< numElements {
switch elementAtIndex(i, associatedPoints:points) {
case NSBezierPathElement.MoveToBezierPathElement:
CGPathMoveToPoint(newPath, nil, points[0].x, points[0].y )
case NSBezierPathElement.LineToBezierPathElement:
CGPathAddLineToPoint(newPath, nil, points[0].x, points[0].y )
bDidClosePath = false
case NSBezierPathElement.CurveToBezierPathElement:
CGPathAddCurveToPoint(newPath, nil, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y )
bDidClosePath = false
case NSBezierPathElement.ClosePathBezierPathElement:
CGPathCloseSubpath(newPath)
bDidClosePath = true
}
if forceClose && !bDidClosePath {
CGPathCloseSubpath(newPath)
}
}
cgPath = CGPathCreateCopy(newPath)
}
return cgPath
}

I can't figure out why the accepted answer adds some sophisticated close-path logic (maybe it is needed under some circumstances), but for those who just need a pristine conversion of the path, here is cleaned version of that code, implemented as a regular method:
- (CGMutablePathRef)CGPathFromPath:(NSBezierPath *)path
{
CGMutablePathRef cgPath = CGPathCreateMutable();
NSInteger n = [path elementCount];
for (NSInteger i = 0; i < n; i++) {
NSPoint ps[3];
switch ([path elementAtIndex:i associatedPoints:ps]) {
case NSMoveToBezierPathElement: {
CGPathMoveToPoint(cgPath, NULL, ps[0].x, ps[0].y);
break;
}
case NSLineToBezierPathElement: {
CGPathAddLineToPoint(cgPath, NULL, ps[0].x, ps[0].y);
break;
}
case NSCurveToBezierPathElement: {
CGPathAddCurveToPoint(cgPath, NULL, ps[0].x, ps[0].y, ps[1].x, ps[1].y, ps[2].x, ps[2].y);
break;
}
case NSClosePathBezierPathElement: {
CGPathCloseSubpath(cgPath);
break;
}
default: NSAssert(0, #"Invalid NSBezierPathElement");
}
}
return cgPath;
}
Btw, I needed this to implement "NSBezierPath stroke contains point" method.
I've looked for this conversion to call CGPathCreateCopyByStrokingPath(), that converts NSBezierPath stroke outline to regular path, so you can test hits on strokes too, and here is the solution:
// stroke (0,0) to (10,0) width 5 --> rect (0, -2.5) (10 x 5)
NSBezierPath *path = [[NSBezierPath alloc] init];
[path moveToPoint:NSMakePoint(0.0, 0.0)];
[path lineToPoint:NSMakePoint(10.0, 0.0)];
[path setLineWidth:5.0];
CGMutablePathRef cgPath = [self CGPathFromPath:path];
CGPathRef strokePath = CGPathCreateCopyByStrokingPath(cgPath, NULL, [path lineWidth], [path lineCapStyle],
[path lineJoinStyle], [path miterLimit]);
CGPathRelease(cgPath);
NSLog(#"%#", NSStringFromRect(NSRectFromCGRect(CGPathGetBoundingBox(strokePath))));
// {{0, -2.5}, {10, 5}}
CGPoint point = CGPointMake(1.0, 1.0);
BOOL hit = CGPathContainsPoint(strokePath, NULL, point, (bool)[path windingRule]);
NSLog(#"%#: %#", NSStringFromPoint(NSPointFromCGPoint(point)), (hit ? #"yes" : #"no"));
// {1, 1}: yes
CGPathRelease(strokePath);
This is similar to QPainterPathStroker from Qt, but for NSBezierPath.

c# Xamarin
private CGPath convertNSBezierPathToCGPath(NSBezierPath sourcePath)
{
CGPath destinationPath = new CGPath();
int i, numElements;
// Then draw the path elements.
numElements = (int)Convert.ToInt64(sourcePath.ElementCount);
if (numElements > 0)
{
CGPath path = new CGPath();
CGPoint[] points;
bool didClosePath = true;
for (i = 0; i < numElements; i++)
{
switch (sourcePath.ElementAt(i, out points))
{
case NSBezierPathElement.MoveTo:
path.MoveToPoint(points[0]);
break;
case NSBezierPathElement.LineTo:
path.MoveToPoint(points[0]);
didClosePath = false;
break;
case NSBezierPathElement.CurveTo:
path.AddCurveToPoint(cp1: points[0], cp2: points[1], points[2]);
didClosePath = false;
break;
case NSBezierPathElement.ClosePath:
path.CloseSubpath();
didClosePath = true;
break;
}
}
if (!didClosePath)
path.CloseSubpath();
destinationPath = new CGPath(path);
}
return destinationPath;
}

Related

How to get an array of AXMenuItems from AXMenu?

For my code I am attempting to get an array of AXMenuItems from an AXMenu (AXUIElementRef). The menu logs successfully, and here is my code:
NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);
CFTypeRef aChildren;
AXUIElementCopyAttributeValue(anAXDockApp, kAXChildrenAttribute, &aChildren);
SafeCFRelease(anAXDockApp);
CFTypeRef aMenu = CFArrayGetValueAtIndex(aChildren, 0);
NSLog(#"aMenu: %#", aMenu);
// Get menu items
CFTypeRef aMenuChildren;
AXUIElementCopyAttributeValue(aMenu, kAXVisibleChildrenAttribute, &aMenuChildren);
for (NSInteger i = 0; i < CFArrayGetCount(aMenuChildren); i++) {
AXUIElementRef aMenuItem = [self copyAXUIElementFrom:aMenu role:kAXMenuItemRole atIndex:i];
NSLog(#"aMenuItem: %#", aMenuItem); // logs (null)
CFTypeRef aTitle;
AXUIElementCopyAttributeValue(aMenuItem, kAXTitleAttribute, &aTitle);
if ([(__bridge NSString *)aTitle isEqualToString:#"New Window"] || [(__bridge NSString *)aTitle isEqualToString:#"New Finder Window"]) /* Crashes here (i can see why)*/{
AXUIElementPerformAction(aMenuItem, kAXPressAction);
[NSThread sleepForTimeInterval:1];
break;
}
}
What is the correct way to get the list of AXMenuItems?
Screenshot of the Accessibility Inspector:
I have figured out an answer, using #Willeke answer of using AXUIElementCopyElementAtPosition() to get the menu. Since there were multiple dock orientations and hiding, I had to create enums in the .h file as it would be easier to read than 0, 1, or 2.
// .h
typedef enum {
kDockPositionBottom,
kDockPositionLeft,
kDockPositionRight,
kDockPositionUnknown
} DockPosition;
typedef enum {
kDockAutohideOn,
kDockAutohideOff
} DockAutoHideState;
Then, I added the methods to get these states in the .m
// .m
- (DockPosition)dockPosition
{
NSRect screenRect = [[NSScreen mainScreen] frame];
NSRect visibleRect = [[NSScreen mainScreen] visibleFrame];
// Dont need to remove menubar height
visibleRect.origin.y = 0;
if (visibleRect.origin.x > screenRect.origin.x) {
return kDockPositionLeft;
} else if (visibleRect.size.width < screenRect.size.width) {
return kDockPositionRight;
} else if (visibleRect.size.height < screenRect.size.height) {
return kDockPositionBottom;
}
return kDockPositionUnknown;
}
- (DockAutoHideState)dockHidden
{
NSString *plistPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Library/Preferences/com.apple.dock.plist"];
NSDictionary *dockDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
CFBooleanRef autohide = CFDictionaryGetValue((__bridge CFDictionaryRef)dockDict, #"autohide");
if (CFBooleanGetValue(autohide) == true) {
return kDockAutohideOn;
}
return kDockAutohideOff;
}
For the dock position unknown, I added in case it was not able to calculate it from the screen positions.
Then, I used a method to get the dock item from the menubar:
- (AXUIElementRef)getDockItemWithName:(NSString *)name
{
NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);
AXUIElementRef aList = [self copyAXUIElementFrom:anAXDockApp role:kAXListRole atIndex:0];
CFTypeRef aChildren;
AXUIElementCopyAttributeValue(aList, kAXChildrenAttribute, &aChildren);
NSInteger itemIndex = -1;
for (NSInteger i = 0; i < CFArrayGetCount(aChildren); i++) {
AXUIElementRef anElement = CFArrayGetValueAtIndex(aChildren, i);
CFTypeRef aResult;
AXUIElementCopyAttributeValue(anElement, kAXTitleAttribute, &aResult);
if ([(__bridge NSString *)aResult isEqualToString:name]) {
itemIndex = i;
}
}
SafeCFRelease(aChildren);
if (itemIndex == -1) return nil;
// We have index now do something with it
AXUIElementRef aReturnItem = [self copyAXUIElementFrom:aList role:kAXDockItemRole atIndex:itemIndex];
SafeCFRelease(aList);
return aReturnItem;
}
This SafeCFRelease() method is a very simple method that checks if the passed value is not nil, then releases (had some issues earlier).
void SafeCFRelease( CFTypeRef cf )
{
if (cf) CFRelease(cf);
}
And this method [copyAXUIElementFrom: role: atIndex:] is a method from #Willeke answer from another one of my questions:
- (AXUIElementRef)copyAXUIElementFrom:(AXUIElementRef)theContainer role:(CFStringRef)theRole atIndex:(NSInteger)theIndex {
AXUIElementRef aResultElement = NULL;
CFTypeRef aChildren;
AXError anAXError = AXUIElementCopyAttributeValue(theContainer, kAXChildrenAttribute, &aChildren);
if (anAXError == kAXErrorSuccess) {
NSUInteger anIndex = -1;
for (id anElement in (__bridge NSArray *)aChildren) {
if (theRole) {
CFTypeRef aRole;
anAXError = AXUIElementCopyAttributeValue((__bridge AXUIElementRef)anElement, kAXRoleAttribute, &aRole);
if (anAXError == kAXErrorSuccess) {
if (CFStringCompare(aRole, theRole, 0) == kCFCompareEqualTo)
anIndex++;
SafeCFRelease(aRole);
}
}
else
anIndex++;
if (anIndex == theIndex) {
aResultElement = (AXUIElementRef)CFRetain((__bridge CFTypeRef)(anElement));
break;
}
}
SafeCFRelease(aChildren);
}
return aResultElement;
}
Taking all this code, I put it into one of my methods:
// Check if in dock (otherwise cant do it)
if ([self isAppOfNameInDock:[appDict objectForKey:#"AppName"]]) {
// Get dock item
AXUIElementRef aDockItem = [self getDockItemWithName:[appDict objectForKey:#"AppName"]];
AXUIElementPerformAction(aDockItem, kAXShowMenuAction);
[NSThread sleepForTimeInterval:0.5];
CGRect aRect;
CFTypeRef aPosition;
AXUIElementCopyAttributeValue(aDockItem, kAXPositionAttribute, &aPosition);
AXValueGetValue(aPosition, kAXValueCGPointType, &aRect.origin);
SafeCFRelease(aPosition);
CFTypeRef aSize;
AXUIElementCopyAttributeValue(aDockItem, kAXSizeAttribute, &aSize);
AXValueGetValue(aSize, kAXValueCGSizeType, &aRect.size);
SafeCFRelease(aSize);
SafeCFRelease(aDockItem);
CGPoint aMenuPoint;
if ([self dockHidden] == kDockAutohideOff) {
switch ([self dockPosition]) {
case kDockPositionRight:
aMenuPoint = CGPointMake(aRect.origin.x - 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionLeft:
aMenuPoint = CGPointMake(aRect.origin.x + aRect.size.width + 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionBottom:
aMenuPoint = CGPointMake(aRect.origin.x + (aRect.size.width / 2), aRect.origin.y - 18);
break;
case kDockPositionUnknown:
aMenuPoint = CGPointMake(0, 0);
break;
}
} else {
NSRect screenFrame = [[NSScreen mainScreen] frame];
switch ([self dockPosition]) {
case kDockPositionRight:
aMenuPoint = CGPointMake(screenFrame.size.width - 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionLeft:
aMenuPoint = CGPointMake(screenFrame.origin.x + 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionBottom:
aMenuPoint = CGPointMake(aRect.origin.x + (aRect.size.width / 2), screenFrame.size.height - 18);
break;
case kDockPositionUnknown:
aMenuPoint = CGPointMake(0, 0);
break;
}
}
if ((aMenuPoint.x != 0) && (aMenuPoint.y != 0)) {
AXUIElementRef _systemWideElement = AXUIElementCreateSystemWide();
AXUIElementRef aMenu;
AXUIElementCopyElementAtPosition(_systemWideElement, aMenuPoint.x, aMenuPoint.y, &aMenu);
SafeCFRelease(_systemWideElement);
// Get menu items
CFTypeRef aMenuChildren;
AXUIElementCopyAttributeValue(aMenu, kAXVisibleChildrenAttribute, &aMenuChildren);
NSRunningApplication *app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:[appDict objectForKey:#"BundleID"]] objectAtIndex:0];
for (NSInteger i = 0; i < CFArrayGetCount(aMenuChildren); i++) {
AXUIElementRef aMenuItem = [self copyAXUIElementFrom:aMenu role:kAXMenuItemRole atIndex:i];
CFTypeRef aTitle;
AXUIElementCopyAttributeValue(aMenuItem, kAXTitleAttribute, &aTitle);
// Supports chrome, safari, and finder
if ([(__bridge NSString *)aTitle isEqualToString:#"New Window"] || [(__bridge NSString *)aTitle isEqualToString:#"New Finder Window"]) {
AXUIElementPerformAction(aMenuItem, kAXPressAction);
NSInteger numberOfWindows = [self numberOfWindowsOpenFromApplicationWithPID:[app processIdentifier]];
// Wait until open
while ([self numberOfWindowsOpenFromApplicationWithPID:[app processIdentifier]] <= numberOfWindows) {
}
break;
}
}
SafeCFRelease(aMenu);
SafeCFRelease(aMenuChildren);
}
}
This is pretty complicated, but it works. I probably can't explain it, but I have stress tested this code and it works quite well.
Answer to the question "How to get an array of AXMenuItems from AXMenu?": aMenuChildren is the list of menu items. To be sure you could filter by role kAXMenuItemRole.
Answer to the question "Why does it not work?": The menu isn't a menu. When you inspect the menu in Axccessibility Inspector, it displays a warning "Parent does not report element as one of its children". The Dock app has one child, a list of dock items.

Convert Objective C to Monotouch (Rounded Table Grouped)

I have the following Objective C code:
CGFloat cornerRadius = 5.f;
cell.backgroundColor = UIColor.clearColor;
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
CGMutablePathRef pathRef = CGPathCreateMutable();
CGRect bounds = CGRectInset(cell.bounds, 10, 0);
BOOL addLine = NO;
if (indexPath.row == 0 && indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1) {
CGPathAddRoundedRect(pathRef, nil, bounds, cornerRadius, cornerRadius);
} else if (indexPath.row == 0) {
CGPathMoveToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMaxY(bounds));
CGPathAddArcToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMinY(bounds), CGRectGetMidX(bounds), CGRectGetMinY(bounds), cornerRadius);
CGPathAddArcToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMinY(bounds), CGRectGetMaxX(bounds), CGRectGetMidY(bounds), cornerRadius);
CGPathAddLineToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds));
addLine = YES;
} else if (indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1) {
CGPathMoveToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMinY(bounds));
CGPathAddArcToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMaxY(bounds), CGRectGetMidX(bounds), CGRectGetMaxY(bounds), cornerRadius);
CGPathAddArcToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds), CGRectGetMaxX(bounds), CGRectGetMidY(bounds), cornerRadius);
CGPathAddLineToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMinY(bounds));
} else {
CGPathAddRect(pathRef, nil, bounds);
addLine = YES;
}
layer.path = pathRef;
CFRelease(pathRef);
layer.fillColor = [UIColor colorWithWhite:1.f alpha:0.8f].CGColor;
if (addLine == YES) {
CALayer *lineLayer = [[CALayer alloc] init];
CGFloat lineHeight = (1.f / [UIScreen mainScreen].scale);
lineLayer.frame = CGRectMake(CGRectGetMinX(bounds)+10, bounds.size.height-lineHeight, bounds.size.width-10, lineHeight);
lineLayer.backgroundColor = tableView.separatorColor.CGColor;
[layer addSublayer:lineLayer];
}
UIView *testView = [[UIView alloc] initWithFrame:bounds];
[testView.layer insertSublayer:layer atIndex:0];
testView.backgroundColor = UIColor.clearColor;
cell.backgroundView = testView;
That I translate into C# (Xamarin):
float cornerRadius = 5f;
cell.BackgroundColor = UIColor.Clear;
CAShapeLayer layer = new CAShapeLayer ();
CGPath pathRef = new CGPath();
RectangleF bounds = new RectangleF(cell.Bounds.X,cell.Bounds.Y, 5, 0);
bool addLine = false;
if (indexPath.Row == 0 && indexPath.Row == tableView.NumberOfRowsInSection(indexPath.Section)-1) {
pathRef.AddRoundedRect (bounds, cornerRadius, cornerRadius);
} else if (indexPath.Row == 0) {
pathRef.MoveToPoint (bounds.GetMinX(), bounds.GetMaxY());
pathRef.AddArcToPoint(bounds.GetMinX(),bounds.GetMinY(), bounds.GetMidX(),bounds.GetMinY(),cornerRadius);
pathRef.AddArcToPoint(bounds.GetMaxX(),bounds.GetMinY(), bounds.GetMaxX(),bounds.GetMidY(),cornerRadius);
pathRef.AddLineToPoint(bounds.GetMaxX(),bounds.GetMaxY());
addLine = true;
} else if (indexPath.Row == tableView.NumberOfRowsInSection(indexPath.Section)-1) {
pathRef.MoveToPoint (bounds.GetMinX(), bounds.GetMinY());
pathRef.AddArcToPoint(bounds.GetMinX(),bounds.GetMaxY(), bounds.GetMidX(),bounds.GetMaxY(),cornerRadius);
pathRef.AddArcToPoint(bounds.GetMaxX(),bounds.GetMaxY(), bounds.GetMaxX(),bounds.GetMidY(),cornerRadius);
pathRef.AddLineToPoint(bounds.GetMaxX(),bounds.GetMinY());
} else {
pathRef.AddRect (bounds);
addLine = true;
}
layer.Path = pathRef;
//CFRelease(pathRef);
layer.FillColor = new CGColor (1f, 0.8f);// UIColor.FromWhiteAlpha (1f, 0.8f);
if (addLine) {
CALayer lineLayer = new CALayer ();
float lineHeight = (1f / UIScreen.MainScreen.Scale);
lineLayer.Frame = new RectangleF (bounds.GetMinX () + 10, bounds.Size.Height - lineHeight, bounds.Size.Width - 10, lineHeight);
lineLayer.BackgroundColor = tableView.SeparatorColor.CGColor;
layer.AddSublayer (lineLayer);
}
UIView testView = new UIView (bounds);
testView.Layer.InsertSublayer (layer, 0);
testView.BackgroundColor = UIColor.Clear;
cell.BackgroundView = testView;
But it's now working, I don't get an app crash neither an error when I build.
I want to achieve the following from this question:
iOS 7 TableView like in Settings App on iPad

Scrambled Output using CoreText

I'm trying to write some text to my subclassed NSView using CoreText. The letters are written over each other and the font size is way too big. Below is the code of my drawRect method. Any idea what I could be doing wrong?
EDIT: Here is an image of the output.
- (void)drawRect:(NSRect)dirtyRect
{
NSGraphicsContext* contextRefNS = [NSGraphicsContext currentContext];
CGContextRef contextRef = (CGContextRef)[contextRefNS graphicsPort];
[[NSColor whiteColor] set];
NSRectFill([self bounds]);
CTFontRef fontRef;
CFStringRef fontNameRef;
fontNameRef = CFStringCreateWithCString(0, "Verdana", kCFStringEncodingASCII);
if (fontNameRef)
{
float fFontSize = 10.0f;
fontRef = CTFontCreateWithName(fontNameRef, fFontSize, 0);
CFRelease(fontNameRef);
}
CFStringRef stringRef = CFStringCreateWithCString(kCFAllocatorDefault, "TestText", kCFStringEncodingASCII);
if (stringRef && fontRef)
{
CGColorRef cgColorRef = CGColorCreateGenericRGB(0,0,0,1);
CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName };
CFTypeRef values[] = { fontRef, cgColorRef };
CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,(const void**)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
unsigned char bAntiAlias = 1;
CFAttributedStringRef attrStr = CFAttributedStringCreate(0, stringRef, attributes);
CFRelease (attributes);
if (attrStr)
{
CTLineRef line = CTLineCreateWithAttributedString(attrStr);
if (line)
{
if (bAntiAlias)
CGContextSetShouldAntialias(contextRef, true);
else
CGContextSetShouldAntialias(contextRef, false);
CGContextSetShouldSmoothFonts(contextRef, true);
CGContextSetTextPosition(contextRef, 100, 200);
CTLineDraw(line, contextRef);
CFRelease(line);
}
CFRelease(attrStr);
}
CFRelease(cgColorRef);
}
CFRelease(stringRef);
CFRelease(fontRef);
}

shouldAutorotateToInterfaceOrientation is not working in iOS 6

In iOS 6, shouldAutorotateToInterfaceOrientation is not working, but it worked fine in iOS 5.0 and 5.1.
What do I need to change for iOS 6? Here is my code:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if([[[SampleApplicationAppDelegate instance].callInfoDictionary valueForKey:IS_CHAT] isEqualToString:NO_RESPONSE])
{
int nAngle = 0;
BOOL bRet = NO;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortrait:
nAngle = 90;
bRet = YES;
NSLog(#".......Preview = %f %f",_previewCamera.frame.size.width,_previewCamera.frame.size.height);
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
NSLog(#"Preview = %f %f",_previewCamera.frame.size.width,_previewCamera.frame.size.height);
break;
case UIInterfaceOrientationPortraitUpsideDown:
nAngle = 270;
bRet = YES;
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
case UIInterfaceOrientationLandscapeLeft:
nAngle = 0;
bRet = YES;
//_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
break;
case UIInterfaceOrientationLandscapeRight:
nAngle = 180;
bRet = YES;
//_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
default:
break;
}
return bRet;
}
if(interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
return YES;
return NO;
}
When I searched for this orientation problem, all I found was this and this, but nothing worked for me :(
Please help .....
EDIT: This is happening because Apple has changed the way of managing the Orientation of UIViewController. In iOS6 orientation handles differently. In iOS6 shouldAutorotateToInterfaceOrientation method is deprecated. View controllers (such as UINavigationController) do not consult their children to determine whether they should autorotate. By default, an app and a view controller’s supported interface orientations are set to UIInterfaceOrientationMaskAll for the iPad idiom and UIInterfaceOrientationMaskAllButUpsideDown for the iPhone idiom.
If you want a specific view to be changed to the desired orientation you have to do some sort of subclass or category and override the autorotation methods to return the desired orientation.
Place this code in your root view controller. This will help the UIViewController to determine its orientation.
//RotationIn_IOS6 is a Category for overriding the default orientation.
#implementation UINavigationController (RotationIn_IOS6)
-(BOOL)shouldAutorotate
{
return [[self.viewControllers lastObject] shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
#end
Now you need to implement below methods (introduced in iOS6) in viewController for orientation
- (BOOL)shouldAutorotate
{
//returns true if want to allow orientation change
return TRUE;
}
- (NSUInteger)supportedInterfaceOrientations
{
//decide number of origination tob supported by Viewcontroller.
return UIInterfaceOrientationMaskAll;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
//from here you Should try to Preferred orientation for ViewController
}
And put your code inside the below method. Whenever device orientation is changed
this method will be called:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation duration:(NSTimeInterval)duration
{
if([[[SampleApplicationAppDelegate instance].callInfoDictionary valueForKey:IS_CHAT] isEqualToString:NO_RESPONSE])
{
int nAngle = 0;
BOOL bRet = NO;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortrait:
nAngle = 90;
bRet = YES;
NSLog(#".......Preview = %f %f",_previewCamera.frame.size.width,_previewCamera.frame.size.height);
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
NSLog(#"Preview = %f %f",_previewCamera.frame.size.width,_previewCamera.frame.size.height);
break;
case UIInterfaceOrientationPortraitUpsideDown:
nAngle = 270;
bRet = YES;
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
case UIInterfaceOrientationLandscapeLeft:
nAngle = 0;
bRet = YES;
//_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
break;
case UIInterfaceOrientationLandscapeRight:
nAngle = 180;
bRet = YES;
//_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
default:
break;
}
}
Edit: check your window, you need to add the controller on window as rootViewController rather than addSubview like below
self.window.rootViewController=viewController;
For more information here's an article about iOS6.0 Beta 2 OTA.
I hope this was helpful.
The way I fixed this problem was to replace the following line when my app starts in Delegate Class
window addSubview: navigationController.view
with
window.rootViewController = navigationController
After I made this change my app started to handle screen rotations
This is because Apple has deprecated shouldautorate method from ios6 use these methods instead
- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0);
- (NSUInteger)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0);
// Returns interface orientation masks.
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0);
I Solved this problem just use this
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if(![AppDelegate instance])
return (interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
if([[[AppDelegate instance].callInfoDictionary valueForKey:IS_CHAT] isEqualToString:NO_RESPONSE])
{
int nAngle = 0;
BOOL bRet = NO;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortrait:
{
nAngle = 90;
bRet = YES;
NSLog(#".......Preview = %f %f",_previewCamera.frame.size.width,_previewCamera.frame.size.height);
NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: #"6.0" options: NSNumericSearch];
if (order == NSOrderedSame || order == NSOrderedDescending)
{
// OS version >= 6.0
NSLog(#"ami iOS 6 er device");
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
}
else
{
// OS version < 6.0
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
}
NSLog(#"Preview = %f %f",_previewCamera.frame.size.width,_previewCamera.frame.size.height);
NSLog(#"-->%s %d",__FUNCTION__,__LINE__);
break;
}
case UIInterfaceOrientationPortraitUpsideDown:
{
nAngle = 270;
bRet = YES;
NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: #"6.0" options: NSNumericSearch];
if (order == NSOrderedSame || order == NSOrderedDescending)
{
// OS version >= 6.0
NSLog(#"ami iOS 6 er device");
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
}
else
{
// OS version < 6.0
_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
}
break;
}
case UIInterfaceOrientationLandscapeLeft:
nAngle = 0;
bRet = YES;
//_previewCamera.transform = CGAffineTransformMakeRotation(M_PI*1.5);
break;
case UIInterfaceOrientationLandscapeRight:
nAngle = 180;
bRet = YES;
//_previewCamera.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
default:
break;
}
return bRet;
}
if(interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
return YES;
return NO;
}
please try this one it will be helpful for you.
write code in you viewcontroller class
-(BOOL)shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscape;
}
Then in appdelegate search this line [window addSubview:viewController.view]
and replace this with window.rootViewController = viewController;
cheers it will work.its simple solution for ios 6
this worked for me
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return [super shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
- (BOOL)shouldAutorotate {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationPortrait) {
// your code for portrait mode
}
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown;
}

Display hidden characters in NSTextView

I am writing a text editor for Mac OS X. I need to display hidden characters in an NSTextView (such as spaces, tabs, and special characters). I have spent a lot of time searching for how to do this but so far I have not found an answer. If anyone could point me in the right direction I would be grateful.
Here's a fully working and clean implementation
#interface GILayoutManager : NSLayoutManager
#end
#implementation GILayoutManager
- (void)drawGlyphsForGlyphRange:(NSRange)range atPoint:(NSPoint)point {
NSTextStorage* storage = self.textStorage;
NSString* string = storage.string;
for (NSUInteger glyphIndex = range.location; glyphIndex < range.location + range.length; glyphIndex++) {
NSUInteger characterIndex = [self characterIndexForGlyphAtIndex: glyphIndex];
switch ([string characterAtIndex:characterIndex]) {
case ' ': {
NSFont* font = [storage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
[self replaceGlyphAtIndex:glyphIndex withGlyph:[font glyphWithName:#"periodcentered"]];
break;
}
case '\n': {
NSFont* font = [storage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
[self replaceGlyphAtIndex:glyphIndex withGlyph:[font glyphWithName:#"carriagereturn"]];
break;
}
}
}
[super drawGlyphsForGlyphRange:range atPoint:point];
}
#end
To install, use:
[myTextView.textContainer replaceLayoutManager:[[GILayoutManager alloc] init]];
To find font glyph names, you have to go to CoreGraphics:
CGFontRef font = CGFontCreateWithFontName(CFSTR("Menlo-Regular"));
for (size_t i = 0; i < CGFontGetNumberOfGlyphs(font); ++i) {
printf("%s\n", [CFBridgingRelease(CGFontCopyGlyphNameForGlyph(font, i)) UTF8String]);
}
Have a look at the NSLayoutManager class. Your NSTextView will have a layout manager associated with it, and the layout manager is responsible for associating a character (space, tab, etc.) with a glyph (the image of that character drawn on the screen).
In your case, you would probably be most interested in the replaceGlyphAtIndex:withGlyph: method, which would allow you to replace individual glyphs.
I wrote a text editor a few years back - here's some meaningless code that should get you looking in (hopefully) the right direction (this is an NSLayoutManager subclass btw - and yes I know it's leaking like the proverbial kitchen sink):
- (void)drawGlyphsForGlyphRange:(NSRange)glyphRange atPoint:(NSPoint)containerOrigin
{
if ([[[[MJDocumentController sharedDocumentController] currentDocument] editor] showInvisibles])
{
//init glyphs
unichar crlf = 0x00B6;
NSString *CRLF = [[NSString alloc] initWithCharacters:&crlf length:1];
unichar space = 0x00B7;
NSString *SPACE = [[NSString alloc] initWithCharacters:&space length:1];
unichar tab = 0x2192;
NSString *TAB = [[NSString alloc] initWithCharacters:&tab length:1];
NSString *docContents = [[self textStorage] string];
NSString *glyph;
NSPoint glyphPoint;
NSRect glyphRect;
NSDictionary *attr = [[NSDictionary alloc] initWithObjectsAndKeys:[NSUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"invisiblesColor"]], NSForegroundColorAttributeName, nil];
//loop thru current range, drawing glyphs
int i;
for (i = glyphRange.location; i < NSMaxRange(glyphRange); i++)
{
glyph = #"";
//look for special chars
switch ([docContents characterAtIndex:i])
{
//space
case ' ':
glyph = SPACE;
break;
//tab
case '\t':
glyph = TAB;
break;
//eol
case 0x2028:
case 0x2029:
case '\n':
case '\r':
glyph = CRLF;
break;
//do nothing
default:
glyph = #"";
break;
}
//should we draw?
if ([glyph length])
{
glyphPoint = [self locationForGlyphAtIndex:i];
glyphRect = [self lineFragmentRectForGlyphAtIndex:i effectiveRange:NULL];
glyphPoint.x += glyphRect.origin.x;
glyphPoint.y = glyphRect.origin.y;
[glyph drawAtPoint:glyphPoint withAttributes:attr];
}
}
}
[super drawGlyphsForGlyphRange:glyphRange atPoint:containerOrigin];
}
I solved the problem of converting between NSGlyphs and the corresponding unichar in the NSTextView. The code below works beautifully and replaces spaces with bullets for visible text:
- (void)drawGlyphsForGlyphRange:(NSRange)range atPoint:(NSPoint)origin
{
NSFont *font = [[CURRENT_TEXT_VIEW typingAttributes]
objectForKey:NSFontAttributeName];
NSGlyph bullet = [font glyphWithName:#"bullet"];
for (int i = range.location; i != range.location + range.length; i++)
{
unsigned charIndex = [self characterIndexForGlyphAtIndex:i];
unichar c =[[[self textStorage] string] characterAtIndex:charIndex];
if (c == ' ')
[self replaceGlyphAtIndex:charIndex withGlyph:bullet];
}
[super drawGlyphsForGlyphRange:range atPoint:origin];
}
Perhaps -[NSLayoutManager setShowsControlCharacters:] and/or -[NSLayoutManager setShowsInvisibleCharacters:] will do what you want.
Here is Pol's solution in Swift:
class MyLayoutManager: NSLayoutManager {
override func drawGlyphsForGlyphRange(glyphsToShow: NSRange, atPoint origin: NSPoint) {
if let storage = self.textStorage {
let s = storage.string
let startIndex = s.startIndex
for var glyphIndex = glyphsToShow.location; glyphIndex < glyphsToShow.location + glyphsToShow.length; glyphIndex++ {
let characterIndex = self.characterIndexForGlyphAtIndex(glyphIndex)
let ch = s[startIndex.advancedBy(characterIndex)]
switch ch {
case " ":
let attrs = storage.attributesAtIndex(characterIndex, effectiveRange: nil)
if let font = attrs[NSFontAttributeName] {
let g = font.glyphWithName("periodcentered")
self.replaceGlyphAtIndex(glyphIndex, withGlyph: g)
}
case "\n":
let attrs = storage.attributesAtIndex(characterIndex, effectiveRange: nil)
if let font = attrs[NSFontAttributeName] {
// let g = font.glyphWithName("carriagereturn")
let g = font.glyphWithName("paragraph")
self.replaceGlyphAtIndex(glyphIndex, withGlyph: g)
}
case "\t":
let attrs = storage.attributesAtIndex(characterIndex, effectiveRange: nil)
if let font = attrs[NSFontAttributeName] {
let g = font.glyphWithName("arrowdblright")
self.replaceGlyphAtIndex(glyphIndex, withGlyph: g)
}
default:
break
}
}
}
super.drawGlyphsForGlyphRange(glyphsToShow, atPoint: origin)
}
}
And to list the glyph names:
func listFonts() {
let font = CGFontCreateWithFontName("Menlo-Regular")
for var i:UInt16 = 0; i < UInt16(CGFontGetNumberOfGlyphs(font)); i++ {
if let name = CGFontCopyGlyphNameForGlyph(font, i) {
print("name: \(name) at index \(i)")
}
}
}

Resources