Dessiner un quadrillage de style iOS 7 de façon programmatique

j'essaie de trouver un moyen de dessiner une icône de style iOS 7 'squircle' de forme programmatique, en utilisant des graphismes de base. Je ne demande pas comment dessiner un rectangle arrondi. Un squircle est un superellipse:

Squircle

qui est légèrement différent d'un rectangle arrondi régulier: Rounded rectangle vs squircle

Music icon in squircle

c'est la formule exacte est facilement disponible. Cependant, je ne peux pas comprendre comment dessiner ce en utilisant, par exemple, un CGPath, sans parler de le remplir, et pouvoir le redimensionner assez facilement. Tout cela tout en étant entièrement exacte avec la formule.

19
demandé sur Cœur 2013-08-22 23:20:21

4 réponses

citation tirée de Wikipedia: Superellipse

Pour n = 1/2, en particulier, chacun des quatre arcs est un courbe de Bézier quadratique défini par les deux axes; par conséquent, chaque arc est un segment de parabole.

alors pourquoi ne pas essayer de faire une approximation de la courbe de Squircle en utilisant des courbes de Bezier? Les deux courbes (Bezier et Squircle) sont définies par les équations paramétriques.

Classe UIBezierPath méthode: addCurveToPoint:controlPoint1:controlPoint2:

ajoute une courbe de Bézier cubique à la trajectoire du récepteur.

NOTE: Utilisation du

j'ai utilisé cette méthode et c'est ce qui s'est passé comme résultat:

red line - rectangle arrondi,blue line - rectangle à partir de 4 courbes de Bezier

Rounded rectangle vs cubic Bezier curve

si ce résultat est intéressé-code de dessin ci-dessous.

NOTE: pour obtenir un résultat plus précis match courbe de Bezier peut être nécessaire pour changer les coordonnées des quatre corner points (maintenant, ils correspondent aux angles du rectangle dans lequel s'inscrit la figure).

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);

//set rect size for draw
float rectSize = 275.;
CGRect rectangle = CGRectMake(CGRectGetMidX(rect) - rectSize/2, CGRectGetMidY(rect) - rectSize/2, rectSize, rectSize);

//Rounded rectangle
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
UIBezierPath* roundedPath = [UIBezierPath bezierPathWithRoundedRect:rectangle cornerRadius:rectSize/4.7];
[roundedPath stroke];

//Rectangle from Fours Bezier Curves
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
UIBezierPath *bezierCurvePath = [UIBezierPath bezierPath];

//set coner points
CGPoint topLPoint = CGPointMake(CGRectGetMinX(rectangle), CGRectGetMinY(rectangle));
CGPoint topRPoint = CGPointMake(CGRectGetMaxX(rectangle), CGRectGetMinY(rectangle));
CGPoint botLPoint = CGPointMake(CGRectGetMinX(rectangle), CGRectGetMaxY(rectangle));
CGPoint botRPoint = CGPointMake(CGRectGetMaxX(rectangle), CGRectGetMaxY(rectangle));

//set start-end points
CGPoint midRPoint = CGPointMake(CGRectGetMaxX(rectangle), CGRectGetMidY(rectangle));
CGPoint botMPoint = CGPointMake(CGRectGetMidX(rectangle), CGRectGetMaxY(rectangle));
CGPoint topMPoint = CGPointMake(CGRectGetMidX(rectangle), CGRectGetMinY(rectangle));
CGPoint midLPoint = CGPointMake(CGRectGetMinX(rectangle), CGRectGetMidY(rectangle));

//Four Bezier Curve
[bezierCurvePath moveToPoint:midLPoint];
[bezierCurvePath addCurveToPoint:topMPoint controlPoint1:topLPoint controlPoint2:topLPoint];
[bezierCurvePath moveToPoint:midLPoint];
[bezierCurvePath addCurveToPoint:botMPoint controlPoint1:botLPoint controlPoint2:botLPoint];
[bezierCurvePath moveToPoint:midRPoint];
[bezierCurvePath addCurveToPoint:topMPoint controlPoint1:topRPoint controlPoint2:topRPoint];
[bezierCurvePath moveToPoint:midRPoint];
[bezierCurvePath addCurveToPoint:botMPoint controlPoint1:botRPoint controlPoint2:botRPoint];

[bezierCurvePath stroke];

CGContextRestoreGState(context);
13
répondu Ruslan Soldatenko 2013-12-20 17:17:22

une version remplie de la réponse acceptée, également portée à Swift:

override func draw(_ rect: CGRect) {
    super.draw(rect)

    guard let context = UIGraphicsGetCurrentContext() else {
        return
    }
    context.saveGState()

    let rect = self.bounds
    let rectSize: CGFloat = rect.width
    let rectangle = CGRect(x: rect.midX - rectSize / 2, y: rect.midY - rectSize / 2, width: rectSize, height: rectSize)

    let topLPoint = CGPoint(x: rectangle.minX, y: rectangle.minY)
    let topRPoint = CGPoint(x: rectangle.maxX, y: rectangle.minY)
    let botLPoint = CGPoint(x: rectangle.minX, y: rectangle.maxY)
    let botRPoint = CGPoint(x: rectangle.maxX, y: rectangle.maxY)

    let midRPoint = CGPoint(x: rectangle.maxX, y: rectangle.midY)
    let botMPoint = CGPoint(x: rectangle.midX, y: rectangle.maxY)
    let topMPoint = CGPoint(x: rectangle.midX, y: rectangle.minY)
    let midLPoint = CGPoint(x: rectangle.minX, y: rectangle.midY)

    let bezierCurvePath = UIBezierPath()
    bezierCurvePath.move(to: midLPoint)
    bezierCurvePath.addCurve(to: topMPoint, controlPoint1: topLPoint, controlPoint2: topLPoint)
    bezierCurvePath.addCurve(to: midRPoint, controlPoint1: topRPoint, controlPoint2: topRPoint)
    bezierCurvePath.addCurve(to: botMPoint, controlPoint1: botRPoint, controlPoint2: botRPoint)
    bezierCurvePath.addCurve(to: midLPoint, controlPoint1: botLPoint, controlPoint2: botLPoint)

    context.setFillColor(UIColor.lightGray.cgColor)
    bezierCurvePath.fill()

    context.restoreGState()
}

parfait pour une utilisation dans une sous-classe uivi.

3
répondu Sunkas 2018-03-22 10:18:18

ce n'est pas une bonne réponse car il ne va pas au cœur de ce que vous demandez qui est la façon de dessiner un superellipse** programmatiquement, mais vous pouvez:

  1. Téléchargez la SVG pour la forme de l'icône iOS7 ici:http://dribbble.com/shots/1127699-iOS-7-icon-shape-PSD
  2. Importer dans votre projet Xcode
  3. ajouter PocketSVG à votre projet:https://github.com/arielelkin/PocketSVG
  4. charger le SVG, et convertissez-le en UIBezierPath, à partir de là, vous pouvez le dimensionner et le transformer comme bon vous semble:

    PocketSVG *myVectorDrawing = [[PocketSVG alloc] initFromSVGFileNamed:@"iOS_7_icon_shape"];
    
    UIBezierPath *myBezierPath = myVectorDrawing.bezier;
    
    // Apply your transforms here:
    [myBezierPath applyTransform:CGAffineTransformMakeScale(2.5, 2.5)];
    [myBezierPath applyTransform:CGAffineTransformMakeTranslation(10, 50)];
    
    CAShapeLayer *myShapeLayer = [CAShapeLayer layer];
    myShapeLayer.path = myBezierPath.CGPath;
    myShapeLayer.strokeColor = [[UIColor redColor] CGColor];
    myShapeLayer.lineWidth = 2;
    myShapeLayer.fillColor = [[UIColor clearColor] CGColor];
    
    [self.view.layer addSublayer:myShapeLayer];  
    

** il est peut-être intéressant de noter que la forme pourrait ne pas être une superellipse exacte de toute façon: http://i.imgur.com/l0ljVRo.png

2
répondu Simon Holroyd 2013-10-18 18:54:34

ce serait assez facile à faire dans OpenGL ES avec un shader. Il suffit de dessiner un quad et de passer dans les X et y comme attributs de vertex. Dans le fragment shader, insérez x et y dans l'équation. Si le résultat <= 1, alors le fragment est à l'intérieur de la forme. Si je peux avoir un peu de temps libre je pourrais essayer ceci et le poster ici.

si vous voulez utiliser CGPath, je pense que la clé est de paramétrer x et y en termes de t, qui va de 0 à 2π. Ensuite, évaluez x et y à intervalles réguliers. Je vais essayer de comprendre cela dans mon temps libre, aussi, mais mon calcul est un peu rouillé.

BTW, je suis certain qu'Apple est en utilisant cette formule. Voir le lien que @millimoose a posté: http://blog.mikeswanson.com/post/62341902567/unleashing-genetic-algorithms-on-the-ios-7-icon

0
répondu bugloaf 2013-12-18 17:12:49