Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have provided an image of how I would like it to appear but have no idea how to do it. Also if possible I would like to know how to constrain such awkward looking buttons to look good on all displays. Thank you all very much!!
enum CustomButtonType {
case topLeft, topRight, bottom
}
let triangelHeight:CGFloat = 75
class CustomButton: UIButton {
private var bgColor:UIColor = .white
private var type:CustomButtonType = .topLeft
init(frame:CGRect, bgColor:UIColor, type:CustomButtonType) {
super.init(frame: frame)
self.bgColor = bgColor
self.type = type
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
switch type {
case .topLeft:
context.move(to: CGPoint(x: rect.minX, y: rect.minY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
context.addLine(to: CGPoint(x: (rect.maxX), y: rect.maxY - triangelHeight))
context.addLine(to: CGPoint(x: (rect.minX), y: rect.maxY))
break
case .topRight:
context.move(to: CGPoint(x: rect.minX, y: rect.minY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
context.addLine(to: CGPoint(x: (rect.maxX), y: rect.maxY ))
context.addLine(to: CGPoint(x: (rect.minX), y: rect.maxY - triangelHeight))
break
case .bottom:
context.move(to: CGPoint(x: rect.minX, y: rect.minY + triangelHeight))
context.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + triangelHeight))
context.addLine(to: CGPoint(x: (rect.maxX), y: rect.maxY ))
context.addLine(to: CGPoint(x: (rect.minX), y: rect.maxY ))
break
}
context.closePath()
context.setFillColor(bgColor.cgColor)
context.fillPath()
}
}
I have created buttons with frame you can use Autolayout.
let topBtnHeight:CGFloat = 400
let aBtn = CustomButton(frame: .init(x: 0, y: 0, width: view.frame.width / 2, height: topBtnHeight), bgColor: .green, type: .topLeft)
view.addSubview(aBtn)
let bBtn = CustomButton(frame: .init(x: view.frame.width / 2, y: 0, width: view.frame.width / 2, height: topBtnHeight), bgColor: .red, type: .topRight)
view.addSubview(bBtn)
let cBtn = CustomButton(frame: .init(x: 0, y: topBtnHeight - triangelHeight, width: view.frame.width , height: view.frame.height - topBtnHeight + triangelHeight), bgColor: .yellow, type: .bottom)
view.addSubview(cBtn)
Related
I'm trying to achive this layout
[LAYOUT TO ACHIEVE]
and right now I have gradient part done, blur part done, and also bezier path mostly done. My question is that I can't replace TAIL from bottom right part to top left part (to be similar like in the picture).
[LAYOUT ACHIEVED]
My Code I wrote for acheiving:
public final class BubbleView: UIView {
public var configuration: Configuration = Configuration.defaultConfiguration {
didSet {
setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
super.backgroundColor = .clear
}
public override func draw(_ rect: CGRect) {
let hasBlurSubview = subviews.compactMap({$0}).contains(where: {$0 is UIVisualEffectView })
guard !hasBlurSubview else {
return
}
let lineWidth = configuration.lineWidth
let bezierPath = UIBezierPath()
bezierPath.lineWidth = lineWidth
let bottom = rect.height - lineWidth
let right = rect.width - lineWidth
let top = 0.0
let left = 0.0
bezierPath.move(to: CGPoint(x: right - 22, y: bottom))
bezierPath.addLine(to: CGPoint(x: 17 + lineWidth, y: bottom))
bezierPath.addCurve(to: CGPoint(x: left, y: bottom - 18), controlPoint1: CGPoint(x: 7.61 + lineWidth, y: bottom), controlPoint2: CGPoint(x: left, y: bottom - 7.61))
bezierPath.addLine(to: CGPoint(x: left, y: 17 + lineWidth))
bezierPath.addCurve(to: CGPoint(x: 17 + borderWidth, y: top), controlPoint1: CGPoint(x: left, y: 7.61 + lineWidth), controlPoint2: CGPoint(x: 7.61 + lineWidth, y: top))
bezierPath.addLine(to: CGPoint(x: right - 21, y: top))
bezierPath.addCurve(to: CGPoint(x: right - 4, y: 17 + lineWidth), controlPoint1: CGPoint(x: right - 11.61, y: top), controlPoint2: CGPoint(x: right - 4, y: 7.61 + lineWidth))
bezierPath.addLine(to: CGPoint(x: right - 4, y: bottom - 11))
bezierPath.addCurve(to: CGPoint(x: right, y: bottom), controlPoint1: CGPoint(x: right - 4, y: bottom - 1), controlPoint2: CGPoint(x: right, y: bottom))
bezierPath.addLine(to: CGPoint(x: right + 0.05, y: bottom - 0.01))
bezierPath.addCurve(to: CGPoint(x: right - 11.04, y: bottom - 4.04), controlPoint1: CGPoint(x: right - 4.07, y: bottom + 0.43), controlPoint2: CGPoint(x: right - 8.16, y: bottom - 1.06))
bezierPath.addCurve(to: CGPoint(x: right - 22, y: bottom), controlPoint1: CGPoint(x: right - 16, y: bottom), controlPoint2: CGPoint(x: right - 19, y: bottom))
bezierPath.close()
let blurEffect = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blurEffect.alpha = configuration.blurAlpha
blurEffect.frame = bezierPath.bounds
let mask = CAShapeLayer()
mask.path = bezierPath.cgPath
blurEffect.layer.mask = mask
insertSubview(blurEffect, at: 0)
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: size)
gradient.colors = configuration.borderGradientColors.map({$0.cgColor})
gradient.startPoint = configuration.borderGradientStartPoint
gradient.endPoint = configuration.borderGradientEndPoint
let shape = CAShapeLayer()
shape.lineWidth = lineWidth
shape.path = bezierPath.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
blurEffect.layer.addSublayer(gradient)
}
}
public class Configuration {
static var defaultConfiguration: Configuration {
return .init(
lineWidth: 2.0,
blurAlpha: 0.95,
borderGradientColors: [Assets.Colors.white05.color, Assets.Colors.white01.color],
borderGradientStartPoint: CGPoint(x: 0.0, y: 0.5),
borderGradientEndPoint: CGPoint(x: 1.0, y: 0.5)
)
}
var lineWidth: CGFloat
var blurAlpha: CGFloat
var borderGradientColors: [UIColor]
var borderGradientStartPoint: CGPoint
var borderGradientEndPoint: CGPoint
init(
lineWidth: CGFloat,
blurAlpha: CGFloat,
borderGradientColors: [UIColor],
borderGradientStartPoint: CGPoint,
borderGradientEndPoint: CGPoint
) {
self.lineWidth = lineWidth
self.blurAlpha = blurAlpha
self.borderGradientColors = borderGradientColors
self.borderGradientStartPoint = borderGradientStartPoint
self.borderGradientEndPoint = borderGradientEndPoint
}
}
Im attempting to animate a UIView along an oval using a bezier curve. The code works except that there is a delay between each loop of the animation. I'm at a loss as to what is causing this.
let myView = UIView()
myView.backgroundColor = UIColor.blue
myView.frame = CGRect(x: 0, y:0, width: 30, height: 30)
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.repeatCount = 1000
animation.duration = 1
myView.layer.add(animation, forKey: "animate along path")
Found a solution. The pause between animation loops only takes place when making a path with the UIBezierPath(ovalIn:) constructor. There's no delay when manually making your own path. So I made my own oval.
Replaced this
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
with this
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.maxY), controlPoint: CGPoint(x: rect.maxX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.midY), controlPoint: CGPoint(x: rect.minX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), controlPoint: CGPoint(x: rect.minX, y: rect.minY))
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.midY), controlPoint: CGPoint(x: rect.maxX, y: rect.minY))
I tried to create a function inside a custom UIButton class to add a shape to an existing button.
func drawStartButton(){
let shape = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 500, y: 0))
path.addLine(to: CGPoint(x: 500, y: -100))
path.addLine(to: CGPoint(x: 250, y: -50))
path.addLine(to: CGPoint(x: 0, y: -100))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
shape.path = path.cgPath
shape.fillColor = redColor.cgColor
shape.frame = self.bounds
self.layer.addSublayer(shape)
}
So far no problem... BUT when i add the layer to the button, the layer is to big of course! How can i "autoresize" the layer to its button? I did expect something like
shape.frame = self.bounds
... but the path still keeps the same size as without the self.bounds.
Your best bet is to subclass the button and then layout your layer in layoutSubviews which gets called when your surrounding frame changes.
final class StartButton: UIButton {
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.frame = bounds
}
}
But I'm noticing that you are dictating a giant shape in terms of your CGPoints. You might have to subclass the layer, too, and redraw based on your bounds in layoutSublayers.
class CustomShapeCAshapeLayer: CAShapeLayer {
func shapeButton(width: CGFloat, height: CGFloat){
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: width, y: 0))
path.addLine(to: CGPoint(x: width, y: height + 30))
path.addLine(to: CGPoint(x: width/2, y: height))
path.addLine(to: CGPoint(x: 0, y: height + 30))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
self.fillColor = redColor.cgColor
self.path = path.cgPath
}
}
main:
let layer = CustomShapeCAshapeLayer()
layer.shapeButton(width: startGameButton.frame.width, height: startGameButton.frame.height)
startGameButton.layer.addSublayer(layer)
I am trying to draw a shape using CGPath and SKShapeNode. However, whatever the lineJoin or lineCap combinations I try to use, the lines have gaps.
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let path = CGMutablePath()
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
path.addCurve(to: NSPoint(x: 66.585, y: -20.3150000000001), control1: NSPoint(x: -60.585, y: -41.765), control2: NSPoint(x: -3.39500000000001, y: -1.88499999999999))
path.addCurve(to: NSPoint(x: 56.545, y: 18.235), control1: NSPoint(x: 66.125, y: -7.09500000000003), control2: NSPoint(x: 62.695, y: 6.16499999999996))
path.addCurve(to: NSPoint(x: -66.585, y: 1.125), control1: NSPoint(x: 56.055, y: 19.1849999999999), control2: NSPoint(x: -15.415, y: 41.765))
path.closeSubpath()
let shapeNode = SKShapeNode(path: path)
shapeNode.fillColor = .red
shapeNode.lineWidth = 10
shapeNode.lineJoin = .round
shapeNode.strokeColor = .white
addChild(shapeNode)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
What am I doing wrong here?
This looks odd to me:
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
You move to (-66.58., 1.125). Then you add a curve where the first point is (-60.585, -41.765), and the first control point is the same as the original point you moved to. It seems like that will cause some odd issues. I'd think you'd want to have the point you move to be the first point of the curve, not its control point.
After spending a whole day trying to work out how to do it I have finally worked out a way of having non-rectangular buttons.
All three buttons at the bottom are on one button. The hitTest then works out which UIBezierPath the click was within then calls a function.
import UIKit
class ViewController: UIViewController {
let path = UIBezierPath()
let path2 = UIBezierPath()
let path3 = UIBezierPath()
let path4 = UIBezierPath()
#IBAction func clicked(_ sender: Any) {
print("Clicked")
}
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
drawShape()
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.checkAction(sender:)))
self.button.addGestureRecognizer(gesture)
}
func drawShape(){
let buttonWidth = button.frame.width
let buttonHeight = buttonWidth * 1.23
path.move(to: CGPoint(x: 0, y: buttonHeight*0.85))
path.addLine(to: CGPoint(x: 0, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path.close()
path2.move(to: CGPoint(x: 0, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: 0, y: buttonHeight*0.85))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path2.close()
path3.move(to: CGPoint(x: 0, y: buttonHeight*0.50))
path3.addLine(to: CGPoint(x: 0, y: buttonHeight*0.68))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path3.close()
path4.move(to: CGPoint(x: 0, y: buttonHeight*0.45))
path4.addLine(to: CGPoint(x: 0, y: buttonHeight*0.50))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.01))
path4.close()
}
func checkAction(sender : UITapGestureRecognizer) {
let location = sender.location(in: button)
print(location)
hitTest(tapLocation: location)
}
public func hitTest(tapLocation:CGPoint){
if path.contains(tapLocation){
print("Button3")
}
if path2.contains(tapLocation){
print("Button2")
}
if path3.contains(tapLocation){
print("Button1")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Then in the file linked to the button:
import UIKit
#IBDesignable
class PushButtonView: UIButton {
override func draw(_ rect: CGRect) {
let buttonWidth = self.frame.width
let buttonHeight = buttonWidth * 1.23
let color1 = hexStringToUIColor(hex: "#e0dfd5")
let color2 = hexStringToUIColor(hex: "#ef6461")
let color3 = hexStringToUIColor(hex: "#e4b363")
let color4 = hexStringToUIColor(hex: "#313638")
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: buttonHeight*0.85))
path.addLine(to: CGPoint(x: 0, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path.close()
color4.setFill()
path.fill()
let path2 = UIBezierPath()
path2.move(to: CGPoint(x: 0, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: 0, y: buttonHeight*0.85))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path2.close()
color3.setFill()
path2.fill()
let path3 = UIBezierPath()
path3.move(to: CGPoint(x: 0, y: buttonHeight*0.50))
path3.addLine(to: CGPoint(x: 0, y: buttonHeight*0.68))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path3.close()
color2.setFill()
path3.fill()
let path4 = UIBezierPath()
path4.move(to: CGPoint(x: 0, y: buttonHeight*0.45))
path4.addLine(to: CGPoint(x: 0, y: buttonHeight*0.50))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.01))
path4.close()
color1.setFill()
path4.fill()
}
func hexStringToUIColor (hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.characters.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
}
Everything works! It may not be the best way of doing it but after 5 hours of trying different things this is whats worked best. However I am now stuck with the problem of having no idea how to manipulate those shapes I have drawn in the external file. I am sure that this has been asked and answered before but I literally have no idea was to search for.
For example if it was javascript I would assume a 'path' drawn within the object 'button' would be accessed by self.button.path (or something similar), however, my understanding is Swift3 doesn't work like that. Am I correct? So if I wanted to change the backgroundColor of the NOTES button/shape with an animate how would I get this effect:
UIView.animate(withDuration: 1, animations: {
self.button.path.color = UIColor.red
}, completion: { finished in
if(finished){
//callFunction()
}
})
Is this possible, or once a path is drawn is it not a changeable object and has to be deleted and redrawn?