web-dev-qa-db-ja.com

UIView(iOS)でハートの形を描く方法は?

いろいろな形を作りたいです。下の画像はその一例です。形を変える方法を教えていただけますか?

enter image description here

11
Rajesh Maurya

UIBezierベースのハートを自分で描いてみたときに出くわしましたが、Instagramの投稿に少し似ているものを探していました。

自分で拡張機能/クラスを作成することになったので、将来これに遭遇した他の人に役立つ場合に備えて、ここで共有したいと思いました。

(これはすべてSwift 3)

まず、UIBezier拡張機能は次のとおりです。

extension UIBezierPath {
    convenience init(heartIn rect: CGRect) {
        self.init()

        //Calculate Radius of Arcs using Pythagoras
        let sideOne = rect.width * 0.4
        let sideTwo = rect.height * 0.3
        let arcRadius = sqrt(sideOne*sideOne + sideTwo*sideTwo)/2

        //Left Hand Curve
        self.addArc(withCenter: CGPoint(x: rect.width * 0.3, y: rect.height * 0.35), radius: arcRadius, startAngle: 135.degreesToRadians, endAngle: 315.degreesToRadians, clockwise: true)

        //Top Centre Dip
        self.addLine(to: CGPoint(x: rect.width/2, y: rect.height * 0.2))

        //Right Hand Curve
        self.addArc(withCenter: CGPoint(x: rect.width * 0.7, y: rect.height * 0.35), radius: arcRadius, startAngle: 225.degreesToRadians, endAngle: 45.degreesToRadians, clockwise: true)

        //Right Bottom Line
        self.addLine(to: CGPoint(x: rect.width * 0.5, y: rect.height * 0.95))

        //Left Bottom Line
        self.close()
    }
}

また、degreesToRadians拡張機能が機能するように、これをプロジェクトのどこかに追加する必要があります。

extension Int {
    var degreesToRadians: CGFloat { return CGFloat(self) * .pi / 180 }
}

これを使用するのは、楕円形の場合と同じ方法でUIBezierを初期化するのと同じくらい簡単です。

let bezierPath = UIBezierPath(heartIn: self.bounds)

これに加えて、これを簡単に表示し、特定の機能を制御するのに役立つクラスを作成しました。完全にIBでレンダリングされ、塗りつぶしの色(色合いの色プロパティを使用)、塗りつぶしが必要かどうか、ストロークの色、およびストロークの幅が上書きされます。

@IBDesignableクラスHeartButton:UIButton {

@IBInspectable var filled: Bool = true
@IBInspectable var strokeWidth: CGFloat = 2.0

@IBInspectable var strokeColor: UIColor?

override func draw(_ rect: CGRect) {
    let bezierPath = UIBezierPath(heartIn: self.bounds)

    if self.strokeColor != nil {
        self.strokeColor!.setStroke()
    } else {
        self.tintColor.setStroke()
    }

    bezierPath.lineWidth = self.strokeWidth
    bezierPath.stroke()

    if self.filled {
        self.tintColor.setFill()
        bezierPath.fill()
    }
}

}

これはUIButtonクラスですが、ボタンにしたくない場合は、これを変更するのは非常に簡単です。

実際の外観は次のとおりです。

Heart Filled

そして、ストロークだけで:

Heart Stroked

12
Carl Goldsmith

PaintCodeを試すことができます。

それを描画すると、PaintCodeがUIBezierPathを生成します。

http://www.paintcodeapp.com/

enter image description here

6
PowHu

enter image description here

ご覧のとおり、2つの簡単な数学関数があります。 drawRectで描画する必要があります:または、 CorePlotフレームワーク を簡単に使用できます。 便利な例です。

4
Pavel Gatilov

UIBezierPathを作成する必要があります。ドキュメントを見てください: https://developer.Apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIBezierPath_class/index.html

描画ガイドのAppleのベジェセクション: https://developer.Apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html

あなたは1つの弧を描くことから始めることができます

+bezierPathWithArcCenter: 

次に、別のアークを追加します。次に、を使用して一番下のピークに線を引きます

-addCurveToPoint:controlPoint1:controlPoint2:

コントロールポイントを使用して、画像のように線をカーブさせます。メソッドのUIBezierPathドキュメントには、使用法を説明するサンプル画像があります。

最後に、開始点に別の行を追加して、パスを閉じます。

リンクされたドキュメントには、パスの描画方法も示されています。 drawRectで。もう1つのオプションは、CAShapeLayerを使用してパスを直接表示することです。

別の解決策は、PaintCodeのようなソフトウェアを使用することです。これにより、画像を視覚的に描画し、コードを自分で作成する代わりに生成することができます。

3
Thyraz

他の侵入者のために、ここにUIBezierPathがハートを描くための拡張機能があります。

このコンポーネントから抽出 https://github.com/ipraba/EPShapes

 public extension UIBezierPath  {

    func getHearts(originalRect: CGRect, scale: Double) -> UIBezierPath {

    //Scaling will take bounds from the originalRect passed
    let scaledWidth = (originalRect.size.width * CGFloat(scale))
    let scaledXValue = ((originalRect.size.width) - scaledWidth) / 2
    let scaledHeight = (originalRect.size.height * CGFloat(scale))
    let scaledYValue = ((originalRect.size.height) - scaledHeight) / 2

    let scaledRect = CGRect(x: scaledXValue, y: scaledYValue, width: scaledWidth, height: scaledHeight)
    self.moveToPoint(CGPointMake(originalRect.size.width/2, scaledRect.Origin.y + scaledRect.size.height))

    self.addCurveToPoint(CGPointMake(scaledRect.Origin.x, scaledRect.Origin.y + (scaledRect.size.height/4)),
        controlPoint1:CGPointMake(scaledRect.Origin.x + (scaledRect.size.width/2), scaledRect.Origin.y + (scaledRect.size.height*3/4)) ,
        controlPoint2: CGPointMake(scaledRect.Origin.x, scaledRect.Origin.y + (scaledRect.size.height/2)) )

    self.addArcWithCenter(CGPointMake( scaledRect.Origin.x + (scaledRect.size.width/4),scaledRect.Origin.y + (scaledRect.size.height/4)),
        radius: (scaledRect.size.width/4),
        startAngle: CGFloat(M_PI),
        endAngle: 0,
        clockwise: true)

    self.addArcWithCenter(CGPointMake( scaledRect.Origin.x + (scaledRect.size.width * 3/4),scaledRect.Origin.y + (scaledRect.size.height/4)),
        radius: (scaledRect.size.width/4),
        startAngle: CGFloat(M_PI),
        endAngle: 0,
        clockwise: true)

    self.addCurveToPoint(CGPointMake(originalRect.size.width/2, scaledRect.Origin.y + scaledRect.size.height),
        controlPoint1: CGPointMake(scaledRect.Origin.x + scaledRect.size.width, scaledRect.Origin.y + (scaledRect.size.height/2)),
        controlPoint2: CGPointMake(scaledRect.Origin.x + (scaledRect.size.width/2), scaledRect.Origin.y + (scaledRect.size.height*3/4)) )
    self.closePath()
    return self
  }
}

0.7(元の長方形の70%)のスケーリングで心臓を描画します

enter image description here

3
iPrabu

Swift 4バージョンから https://github.com/ipraba/EPShapes

func getHearts(_ originalRect: CGRect, scale: Double) -> UIBezierPath {


    let scaledWidth = (originalRect.size.width * CGFloat(scale))
    let scaledXValue = ((originalRect.size.width) - scaledWidth) / 2
    let scaledHeight = (originalRect.size.height * CGFloat(scale))
    let scaledYValue = ((originalRect.size.height) - scaledHeight) / 2

    let scaledRect = CGRect(x: scaledXValue, y: scaledYValue, width: scaledWidth, height: scaledHeight)

    self.move(to: CGPoint(x: originalRect.size.width/2, y: scaledRect.Origin.y + scaledRect.size.height))


    self.addCurve(to: CGPoint(x: scaledRect.Origin.x, y: scaledRect.Origin.y + (scaledRect.size.height/4)),
        controlPoint1:CGPoint(x: scaledRect.Origin.x + (scaledRect.size.width/2), y: scaledRect.Origin.y + (scaledRect.size.height*3/4)) ,
        controlPoint2: CGPoint(x: scaledRect.Origin.x, y: scaledRect.Origin.y + (scaledRect.size.height/2)) )

    self.addArc(withCenter: CGPoint( x: scaledRect.Origin.x + (scaledRect.size.width/4),y: scaledRect.Origin.y + (scaledRect.size.height/4)),
        radius: (scaledRect.size.width/4),
        startAngle: CGFloat(Double.pi),
        endAngle: 0,
        clockwise: true)

    self.addArc(withCenter: CGPoint( x: scaledRect.Origin.x + (scaledRect.size.width * 3/4),y: scaledRect.Origin.y + (scaledRect.size.height/4)),
        radius: (scaledRect.size.width/4),
        startAngle: CGFloat(Double.pi),
        endAngle: 0,
        clockwise: true)

    self.addCurve(to: CGPoint(x: originalRect.size.width/2, y: scaledRect.Origin.y + scaledRect.size.height),
        controlPoint1: CGPoint(x: scaledRect.Origin.x + scaledRect.size.width, y: scaledRect.Origin.y + (scaledRect.size.height/2)),
        controlPoint2: CGPoint(x: scaledRect.Origin.x + (scaledRect.size.width/2), y: scaledRect.Origin.y + (scaledRect.size.height*3/4)) )

    self.close()

    return self
}
1
Thien Chu

これはSwift 5.2のSwiftUIに適合したバージョンです

AddArcが時計回りに逆になっていることに注意してください

struct Heart : Shape {
  func path(in rect: CGRect) -> Path {
    var path = Path()

    path.move(to: CGPoint(x: rect.midY, y: rect.maxY ))

    path.addCurve(to: CGPoint(x: rect.minX, y: rect.height/4),
        control1:CGPoint(x: rect.midX, y: rect.height*3/4) ,
        control2: CGPoint(x: rect.minX, y: rect.midY) )


    path.addArc(center: CGPoint( x: rect.width/4,y: rect.height/4),
        radius: (rect.width/4),
        startAngle: Angle(radians: Double.pi),
        endAngle: Angle(radians: 0),
        clockwise: false)

    path.addArc(center: CGPoint( x: rect.width * 3/4,y: rect.height/4),
        radius: (rect.width/4),
        startAngle: Angle(radians: Double.pi),
        endAngle: Angle(radians: 0),
        clockwise: false)

    path.addCurve(to: CGPoint(x: rect.midX, y: rect.height),
                  control1: CGPoint(x: rect.width, y: rect.midY),
                  control2: CGPoint(x: rect.midX, y: rect.height*3/4) )

     return path
  }
}
0
Ryan Heitner

私はここでWolframにあるハートの形が好きです: https://mathworld.wolfram.com/HeartCurve.html

これは以下の方程式の例であり、そのページにあるハートの形のコレクションの右下の例です。

x = 16sin ^ 3t
y = 13cost-5cos(2t)-2cos(3t)-cos(4t)。

検索してみると、この正確な形状を描くための例がWeb上にあまりないことに気付きました。また、正確なハートを取得するために使用できるSwift)で記述された独自のソリューションがあります。形状。心臓の輪郭を2ピクセルの線幅でトレースする方法を説明します。このソリューションでは、画像を保持するために特別に作成されたUIImageを使用します。その画像を初期化して、背景をクリアにしてオーバーレイできるようにします。境界線をトレースするだけなので、内側は透明です。塗りつぶしたい(透明ではない)場合は、パスをなでる代わりに塗りつぶします。

変数を使用して心臓の中心、スケール、およびUIImageで心臓の形状を移動するための必要なXおよびYオフセットを定義するコードを共有します。少なくとも200x 200サイズのUIImageから始めて、Xcodeデバッガーで画像を調べて、目的のサイズが適切に中央に配置され、適切なスケールであるかどうかを確認することをお勧めします。必要に応じて変数を微調整して、必要なサイズのハートを取得します。

The UIImage with the heart trace, and transparent background, as seen in the debugger.

余談ですが、アニメーション化のために、生成されたUIImageをUIImageViewに詰め込むことを検討してください。

まず、空白のUIImageを取得します。私はどんな機能の外でも私のものを宣言するので、それを簡単に使うことができます。

var myHeartUIImage = UIImage()

ViewDidLoadの内部で、その画像を初期化して背景をクリアにし、サイズを指定します。私のすべての変数は、50 x50フレームで心臓が美しく見えるように調整されています。

    // First get a non nil image, so I just put a clear background inside and it is not nil anymore.
    myHeartUIImage = UIImage(color: .clear, size: CGSize(width: 50, height: 50))!

    // Now draw the heart on top of the clear backbround
    myHeartUIImage = createAHeartTrace(startingImage: myHeartUIImage)

CreateAHeartTraceのコードは次のとおりです。ハートの境界線上の360ポイントを使用して描画します。これは私の使用と規模には問題ありません。 iPad Proのような非常に高解像度のデバイスで大きなハートを表示している場合は、描画ポイントが1000以上になる可能性があります。 moveToPointが最初のポイントを提供するため、ループは1 ... 359になり、合計360ポイントを取得することに注意してください。

public func createAHeartTrace(startingImage: UIImage) -> UIImage
{
    // Create a context of the starting image size and set it as the current one
    UIGraphicsBeginImageContext(startingImage.size)

    let centeringValueForX : CGFloat = 9
    let centeringValueForY : CGFloat = -24

    let centerPointXYValue : CGFloat = 60.0
    let scaleFactorHere : CGFloat = 1.85

    var pointOnHeart = getPointOnHeartShape(thisAngle: 0, thisCenter: CGPoint(x: centerPointXYValue, y: centerPointXYValue), thisScaleFactor: scaleFactorHere)

    // Get the current context
    let context = UIGraphicsGetCurrentContext()!

    context.setLineWidth(2.0)
    context.setStrokeColor(UIColor.white.cgColor)
    context.move(to: CGPoint(x: pointOnHeart.x - centeringValueForX, y: pointOnHeart.y + centeringValueForY))

    for angle in 1...359 {

        pointOnHeart = getPointOnHeartShape(thisAngle: CGFloat(angle), thisCenter: CGPoint(x: centerPointXYValue, y: centerPointXYValue), thisScaleFactor: scaleFactorHere)

        context.addLine(to: CGPoint(x: pointOnHeart.x - centeringValueForX, y: pointOnHeart.y + centeringValueForY))
    }

    context.strokePath()

    // Save the context as a new UIImage
    var myImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    // Return modified image
    return myImage!

}// ends createAHeartTrace

CreateAHeartTraceがfuncgetPointOnHeartShapeを合計360回呼び出すことに注意してください。これがgetPointOnHeartShapeです:

public func getPointOnHeartShape(thisAngle : CGFloat, thisCenter: CGPoint, thisScaleFactor: CGFloat) -> CGPoint
{
    var tempAngle : CGFloat
    tempAngle = thisAngle.degreesToRadians

    let cubedTerm = sin(tempAngle) * sin(tempAngle) * sin(tempAngle)

    let pointX : CGFloat = thisCenter.x + thisScaleFactor * 16.0 * cubedTerm

    let pointY : CGFloat = thisCenter.y + thisScaleFactor * (-13*(cos(tempAngle)) + 5*(cos(2*tempAngle)) + 2*(cos(3*tempAngle)) + 1*(cos(4*tempAngle)))

    let returnPoint = CGPoint(x: pointX, y: pointY)

    return returnPoint

} // ends getPointOnHeartShape

また、度をラジアンに変換するには、拡張子が必要になる可能性があります。

extension FloatingPoint 
{
    var degreesToRadians: Self { return self * .pi / 180 }
}

これらの同じ線に沿ったボーナスコードとして、5ポイントの星(米国旗と同じ鮮明な形状)を描画したい場合は、以下の関数が適切に機能します。繰り返しになりますが、必要に応じてTweakへのスケーリングとオフセットがあり、描画は透明な中央領域を持つ境界線のトレースです。デザインの残りの部分はまったく同じです。 nil以外のUIImageを作成して描画を保持し、createAFivePointStarを使用して描画します。必要に応じてストロークの色を選択します。

The star shape, as seen in the debugger.

public func createAFivePointStar(startingImage: UIImage) -> UIImage
{
    let drawingScaleFactor : CGFloat = 0.11

    // Create a context of the starting image size and set it as the current one
    UIGraphicsBeginImageContext(startingImage.size)

    // Get the current context
    let context = UIGraphicsGetCurrentContext()!

    let centeringValueForX : CGFloat = 25
    let centeringValueForY : CGFloat = 16

    context.setLineWidth(4.0)
    context.setStrokeColor(UIColor.white.cgColor)
    context.move(to: CGPoint(x: 694 * drawingScaleFactor - centeringValueForX, y: 106 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 750 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 920 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 789 * drawingScaleFactor - centeringValueForX, y: 372 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 835 * drawingScaleFactor - centeringValueForX, y: 530 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 694 * drawingScaleFactor - centeringValueForX, y: 438 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 553 * drawingScaleFactor - centeringValueForX, y: 530 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 599 * drawingScaleFactor - centeringValueForX, y: 372 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 468 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 637 * drawingScaleFactor - centeringValueForX, y: 267 * drawingScaleFactor + centeringValueForY))
    context.addLine(to: CGPoint(x: 694 * drawingScaleFactor - centeringValueForX, y: 106 * drawingScaleFactor + centeringValueForY))
    context.strokePath()

    // Save the context as a new UIImage
    var myImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    // Return modified image
    return myImage!

} // ends createAFivePointStar
0
user3408691