Related
UPDATE: This does not happen if I set only the board as the main view. Why would this happen when it is a subview?
I have a view that has a game board that you draw over to select characters. To do this I put a UIImageView over the game board and draw the line in touches moved using a helper function. It works, however, while drawing the line, everything that came before it slowly fades and also moves upwards.
It looks like this:
This is what the draw function looks like and it is called in touches moved:
//Helper function that draws a line which is used while the user is selecting letters
func drawLine(fromPoint: CGPoint, toPoint: CGPoint){
UIGraphicsBeginImageContext(drawingView.frame.size)
//get a context
guard let context: CGContext = UIGraphicsGetCurrentContext() else {
print("failed to get image context to draw line")
return
}
self.drawingView.image?.draw(in: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
//set the line
context.move(to: fromPoint)
context.addLine(to: toPoint)
//customize line
context.setLineCap(CGLineCap.round)
context.setLineWidth(4)
context.setStrokeColor(UIColor.blue.cgColor)
context.setBlendMode(CGBlendMode.normal)
//draw the line
context.strokePath()
drawingView.image = UIGraphicsGetImageFromCurrentImageContext()
//close
UIGraphicsEndImageContext()
//print("Drew Line")
}
Heres all the code for the view:
/**
GameControllerDelegate is used to communicate when the user has chosen a word
*/
protocol GameControllerDelegate: AnyObject {
func wordPicked(letters: [String], moves: [(row: Int, column: Int)])
}
/**
GameViewDelegate is used to let the gameview know a user has picked a word using the board which is a subview
*/
protocol GameViewDelegate: AnyObject {
func wordPickedInBoard(letters: [String], moves: [(row: Int, column: Int)])
}
/**
Game view class holds all the subviews of the Game view as well as provides
getters and setters for each important data
*/
class GameView: UIView , GameViewDelegate{
//the board part
let boardView: BoardView = {
let boardView = BoardView()
boardView.backgroundColor = UIColor.white
boardView.translatesAutoresizingMaskIntoConstraints = true
return boardView
}()
//the score part
let scoreView: ScoreView = {
let scoreView = ScoreView()
scoreView.backgroundColor = UIColor.white
scoreView.translatesAutoresizingMaskIntoConstraints = true
return scoreView
}()
//two other vairables to assist in sizing the subviews
var minusTop: CGFloat = 0
var minusBottom: CGFloat = 0
//the board for the game
var board: [[String]] {
set
{
boardView.boardStrings = newValue
boardView.generateLabels()
setNeedsDisplay()
}
get { return boardView.boardStrings }
}
//the score
var score: Int {
set
{
scoreView.gameScore.text = " Score: " + String(newValue)
}
get
{
let indexStartOfNumber = scoreView.gameScore.text!.index((scoreView.gameScore.text!.startIndex), offsetBy: 9)
let numString = scoreView.gameScore.text?[indexStartOfNumber...]
return Int(String(describing: numString))!
}
}
//delegate for alerting the controller that a word was picked
var delegate: GameControllerDelegate? = nil
init(frame: CGRect, minusTop: CGFloat, minusBottom: CGFloat){
super.init(frame: frame)
boardView.delegate = self
self.minusTop = minusTop
self.minusBottom = minusBottom
addSubview(boardView)
addSubview(scoreView)
}
override init(frame: CGRect){
super.init(frame: frame)
boardView.delegate = self
addSubview(boardView)
addSubview(scoreView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("It's Apple. What did you expect?")
}
//manually layout the alarmpreview
override func layoutSubviews() {
var cursor: CGPoint = .zero
let scoreViewHeight = bounds.height/10
let boardHeight = bounds.height - minusTop - minusBottom - scoreViewHeight
cursor.y += minusTop
boardView.frame = CGRect(x: cursor.x, y: cursor.y, width: bounds.width, height: boardHeight)
cursor.y += boardHeight
scoreView.frame = CGRect(x: cursor.x, y: cursor.y, width: bounds.width, height: scoreViewHeight)
}
//function to catch when a word is picked in the board
func wordPickedInBoard(letters: [String], moves: [(row: Int, column: Int)]) {
//forward the info via the delegate
delegate?.wordPicked(letters: letters, moves: moves)
}
}
/**
Score view shows the users current score
*/
class ScoreView: UIView {
/*var scoreTitle: UILabel = {
let scoreTitle = UILabel()
scoreTitle.backgroundColor = UIColor.white
scoreTitle.text = "Score: "
return scoreTitle
}()*/
var gameScore: UILabel = {
let gameScore = UILabel()
gameScore.backgroundColor = UIColor.white
gameScore.text = " Score: 0"
return gameScore
}()
//init
override init(frame: CGRect){
super.init(frame: frame)
addSubview(gameScore)
//addSubview(scoreTitle)
}
required init?(coder aDecoder: NSCoder) {
fatalError("It's Apple. What did you expect?")
}
override func layoutSubviews() {
let cursor: CGPoint = .zero
gameScore.frame = CGRect(x: cursor.x, y: cursor.y, width: bounds.width, height: bounds.height)
}
}
/**
VoardView holds all the cells of the board and communicates when a user draws on it
*/
class BoardView: UIView {
var drawingView: UIImageView = {
let drawingView = UIImageView()
drawingView.backgroundColor = UIColor.white.withAlphaComponent(0.0)
return drawingView
}()
//delegate to communicate with the superview
var delegate: GameViewDelegate? = nil
//variables to aid in tracking the user moves
var lastPoint = CGPoint.zero
var looped = false
var boardStrings:[[String]] = []
var board:[[UILabel]] = Array(repeating: Array(repeating: UILabel(), count: 9), count: 12)
var curMoves: [(row: Int, column: Int)] = []
var highlightedArea: [(row: Int, column: Int)] = []
//centralized colors
let unHighlightedColor = UIColor.lightGray
let highlightedColor = UIColor.yellow
//Initialize this view
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
initBoard()
//self.boardStrings = generateBoard()
//generateLabels(boardStrings: self.boardStrings)
for i in 0...11 {
for j in 0...8 {
addSubview(board[i][j])
}
}
addSubview(drawingView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("It's Apple. What did you expect?")
}
//override the draw function, we draw the grid as well as the letters here
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context: CGContext = UIGraphicsGetCurrentContext() else {
print("failed to attain draw context")
return
}
//8 verticle lines
for i in 1 ... 9 {
let fromPoint = CGPoint(x: bounds.width * CGFloat(Double(i) * 1.0/9.0), y: 0)
let toPoint = CGPoint(x: bounds.width * CGFloat(Double(i) * 1.0/9.0), y: bounds.height)
context.move(to: fromPoint)
context.addLine(to: toPoint)
context.strokePath()
}
//draw 11 horizontal lines
for i in 1 ... 12 {
let fromPoint = CGPoint(x: 0, y: bounds.height * CGFloat(Double(i) * 1.0/12.0))
let toPoint = CGPoint(x: bounds.width, y: bounds.height * CGFloat(Double(i) * 1.0/12.0))
context.move(to: fromPoint)
context.addLine(to: toPoint)
context.strokePath()
}
}
//here we manually layout all the subviews that go into the preview
override func layoutSubviews() {
var cursor: CGPoint = .zero
let width = bounds.width/9
let height = bounds.height/12
for i in 0 ... 11 {
for j in 0 ... 8 {
board[i][j].frame = CGRect(x: cursor.x, y: cursor.y, width: width, height: height)
cursor.x += bounds.width * 1.0/9.0
}
cursor.x = 0
cursor.y += bounds.height * 1.0/12.0
}
drawingView.frame = self.bounds
}
//handle the beginning of a user move
override func touchesBegan(_ touches: Set<UITouch>, with: UIEvent?) {
looped = false
if let touch = touches.first as? UITouch {
lastPoint = touch.location(in: self)
//find what row we are in
let buttonHeight = bounds.height/12
let buttonWidth = bounds.width/9
let row: Int = Int(lastPoint.y/buttonHeight)
let column: Int = Int(lastPoint.x/buttonWidth)
curMoves.append((row: row, column: column))
highlightLabel(row: row, column: column)
setNeedsDisplay()
}
}
//handle when the user is moving. Calculate coordintes to drive the move the player is making
override func touchesMoved(_ touches: Set<UITouch>, with: UIEvent?) {
if !looped {
if let touch = touches.first as? UITouch {
let currentPoint = touch.location(in: self)
drawLine(fromPoint: lastPoint, toPoint: currentPoint)
//find what row we are in
let buttonHeight = bounds.height/12
let buttonWidth = bounds.width/9
let row: Int = Int(lastPoint.y/buttonHeight)
let column: Int = Int(lastPoint.x/buttonWidth)
if curMoves[curMoves.count - 1] != (row: row, column: column) {
//if the user is backstepping
if curMoves.count > 1 {
if curMoves[curMoves.count - 2] == (row: row, column: column) {
let pos = curMoves.remove(at: curMoves.count - 1)
unHeighlightLabel(row: pos.row, column: pos.column)
}
//if the user tried to make a loop
else if curMoves.contains(where: {$0 == (row: row, column: column)}){
for pos in curMoves {
unHeighlightLabel(row: pos.row, column: pos.column)
}
print("User tried to make a loop")
looped = true
}
else {
curMoves.append((row: row, column: column))
if board[row][column].backgroundColor != UIColor.green{
highlightLabel(row: row, column: column)
}
}
}
//if the user tried to make a loop
/*else if curMoves.contains(where: {$0 == (row: row, column: column)}){
for pos in curMoves {
unHeighlightLabel(row: pos.row, column: pos.column)
}
print("User tried to make a loop")
looped = true
}*/
else {
curMoves.append((row: row, column: column))
if board[row][column].backgroundColor != UIColor.green{
highlightLabel(row: row, column: column)
}
}
}
lastPoint = currentPoint
}
}
setNeedsDisplay()
}
//handle the end of a player move
override func touchesEnded(_ touches: Set<UITouch>, with: UIEvent?) {
if !looped {
//derive word
var word: [String] = []
for move in curMoves {
//if it contains blank put some garbage in there
if !boardStrings[move.row][move.column].contains("Blank") {
word.append(board[move.row][move.column].text!)
}
else {
word.append("###")
}
}
//let superview know
delegate?.wordPickedInBoard(letters: word, moves: curMoves)
}
//reset
for move in curMoves {
if board[move.row][move.column].backgroundColor != UIColor.green{
unHeighlightLabel(row: move.row, column: move.column)
}
}
curMoves = []
looped = false
drawingView.image = nil
setNeedsDisplay()
}
//Helper functions that changes the background color of the specified label to a centralized color
func highlightLabel(row: Int, column: Int){
board[row][column].backgroundColor = highlightedColor
}
func unHeighlightLabel(row: Int, column: Int) {
board[row][column].backgroundColor = unHighlightedColor
}
//Helper function that draws a line which is used while the user is selecting letters
func drawLine(fromPoint: CGPoint, toPoint: CGPoint){
UIGraphicsBeginImageContext(drawingView.frame.size)
//get a context
guard let context: CGContext = UIGraphicsGetCurrentContext() else {
print("failed to get image context to draw line")
return
}
self.drawingView.image?.draw(in: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
//set the line
context.move(to: fromPoint)
context.addLine(to: toPoint)
//customize line
context.setLineCap(CGLineCap.round)
context.setLineWidth(4)
context.setStrokeColor(UIColor.blue.cgColor)
context.setBlendMode(CGBlendMode.normal)
//draw the line
context.strokePath()
drawingView.image = UIGraphicsGetImageFromCurrentImageContext()
//close
UIGraphicsEndImageContext()
//print("Drew Line")
}
//helper function that initilizes the board layout
func initBoard() {
for i in 0 ... 11 {
for j in 0 ... 8 {
board[i][j] = UILabel()
board[i][j].text = ""
}
}
}
/**
This function takes the generated board strings and converts them into their proper labels
*/
func generateLabels() {
highlightedArea = []
for i in 0 ... 11 {
for j in 0 ... 8 {
//board[i][j] = UILabel()
board[i][j].textAlignment = .center
board[i][j].layer.borderColor = UIColor.black.cgColor
board[i][j].layer.borderWidth = 1.0;
if boardStrings[i][j].hasSuffix("^") {//.characters.contains("^"){
//highlight the label
board[i][j].backgroundColor = UIColor.green
//set the text
let index = boardStrings[i][j].index(boardStrings[i][j].startIndex, offsetBy: boardStrings[i][j].count - 1)
let range = boardStrings[i][j].startIndex..<index
let letter = String(boardStrings[i][j][range])
board[i][j].backgroundColor = UIColor.green
board[i][j].text = letter
highlightedArea.append((row: i, column: j))
}
else if boardStrings[i][j] != "Blank" {
board[i][j].backgroundColor = unHighlightedColor
board[i][j].text = boardStrings[i][j]
}
else {
//leave it blank
board[i][j].backgroundColor = unHighlightedColor
board[i][j].text = ""
}
}
}
}
}
Recently experienced the same problem. It might be too late to help you out, but hopefully someone finds this useful someday.
Using floor() for height and width when setting the image view's bounds(image view you want to draw into) worked for me. Did this in objective C... don't know the floor() equivalent for swift.
I'm using the Swift Chart. I'd like to modify it to allow the user to select a range. The idea is to touch, swipe left/right, and then lift your finger. This should highlight the area swiped and provide a way to get the beginning and ending values of the swipe. I expect I'll need to modify the touchesBegan() and touchesEnded() events, but I don't know how.
Here's what I did to make this work:
I added range selection variables to the class
// Range selection
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
I modified touchesBegan()
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
And added a routine to touchesEnded()
handleRangeTouchesEnded(touches, event: event)
Here's the full code:
// Chart.swift
//
// Created by Giampaolo Bellavite on 07/11/14.
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
import UIKit
public protocol ChartDelegate: class {
func didTouchChart(_ chart: Chart, indexes: [Int?], x: Float, left: CGFloat)
func didFinishTouchingChart(_ chart: Chart)
func didEndTouchingChart(_ chart: Chart)
}
typealias ChartPoint = (x: Float, y: Float)
public enum ChartLabelOrientation {
case horizontal
case vertical
}
#IBDesignable open class Chart: UIControl {
#IBInspectable
open var identifier: String?
open var series: [ChartSeries] = [] {
didSet {
setNeedsDisplay()
}
}
open var xLabels: [Float]?
open var xLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var xLabelsTextAlignment: NSTextAlignment = .left
open var xLabelsOrientation: ChartLabelOrientation = .horizontal
open var xLabelsSkipLast: Bool = true
open var xLabelsSkipAll: Bool = true
open var yLabels: [Float]?
open var yLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var yLabelsOnRightSide: Bool = false
open var labelFont: UIFont? = UIFont.systemFont(ofSize: 12)
#IBInspectable
open var labelColor: UIColor = UIColor.black
#IBInspectable
open var axesColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
#IBInspectable
open var gridColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
open var showXLabelsAndGrid: Bool = true
open var showYLabelsAndGrid: Bool = true
open var bottomInset: CGFloat = 20
open var topInset: CGFloat = 20
#IBInspectable
open var lineWidth: CGFloat = 2
weak open var delegate: ChartDelegate?
open var minX: Float?
open var minY: Float?
open var maxX: Float?
open var maxY: Float?
open var highlightLineColor = UIColor.gray
open var highlightLineWidth: CGFloat = 0.5
open var areaAlphaComponent: CGFloat = 0.1
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
fileprivate var highlightShapeLayer: CAShapeLayer!
fileprivate var layerStore: [CAShapeLayer] = []
fileprivate var drawingHeight: CGFloat!
fileprivate var drawingWidth: CGFloat!
fileprivate var min: ChartPoint!
fileprivate var max: ChartPoint!
typealias ChartLineSegment = [ChartPoint]
override public init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
convenience public init() {
self.init(frame: .zero)
commonInit()
}
private func commonInit() {
backgroundColor = UIColor.clear
contentMode = .redraw // redraw rects on bounds change
}
override open func draw(_ rect: CGRect) {
#if TARGET_INTERFACE_BUILDER
drawIBPlaceholder()
#else
drawChart()
#endif
}
open func add(_ series: ChartSeries) {
self.series.append(series)
}
open func add(_ series: [ChartSeries]) {
for s in series {
add(s)
}
}
open func removeSeriesAt(_ index: Int) {
series.remove(at: index)
}
open func removeAllSeries() {
series = []
}
open func valueForSeries(_ seriesIndex: Int, atIndex dataIndex: Int?) -> Float? {
if dataIndex == nil { return nil }
let series = self.series[seriesIndex] as ChartSeries
return series.data[dataIndex!].y
}
fileprivate func drawIBPlaceholder() {
let placeholder = UIView(frame: self.frame)
placeholder.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1)
let label = UILabel()
label.text = "Chart"
label.font = UIFont.systemFont(ofSize: 28)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
label.sizeToFit()
label.frame.origin.x += frame.width/2 - (label.frame.width / 2)
label.frame.origin.y += frame.height/2 - (label.frame.height / 2)
placeholder.addSubview(label)
addSubview(placeholder)
}
fileprivate func drawChart() {
drawingHeight = bounds.height - bottomInset - topInset
drawingWidth = bounds.width
let minMax = getMinMax()
min = minMax.min
max = minMax.max
highlightShapeLayer = nil
// Remove things before drawing, e.g. when changing orientation
for view in self.subviews {
view.removeFromSuperview()
}
for layer in layerStore {
layer.removeFromSuperlayer()
}
layerStore.removeAll()
// Draw content
for (index, series) in self.series.enumerated() {
// Separate each line in multiple segments over and below the x axis
let segments = Chart.segmentLine(series.data as ChartLineSegment, zeroLevel: series.colors.zeroLevel)
segments.forEach({ segment in
let scaledXValues = scaleValuesOnXAxis( segment.map({ return $0.x }) )
let scaledYValues = scaleValuesOnYAxis( segment.map({ return $0.y }) )
if series.line {
drawLine(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
if series.area {
drawArea(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
})
}
drawAxes()
if showXLabelsAndGrid && (xLabels != nil || series.count > 0) {
drawLabelsAndGridOnXAxis()
}
if showYLabelsAndGrid && (yLabels != nil || series.count > 0) {
drawLabelsAndGridOnYAxis()
}
}
fileprivate func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
// Start with user-provided values
var min = (x: minX, y: minY)
var max = (x: maxX, y: maxY)
// Check in datasets
for series in self.series {
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let yValues = series.data.map({ (point: ChartPoint) -> Float in
return point.y })
let newMinX = xValues.min()!
let newMinY = yValues.min()!
let newMaxX = xValues.max()!
let newMaxY = yValues.max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
// Check in labels
if xLabels != nil {
let newMinX = (xLabels!).min()!
let newMaxX = (xLabels!).max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
}
if yLabels != nil {
let newMinY = (yLabels!).min()!
let newMaxY = (yLabels!).max()!
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
if min.x == nil { min.x = 0 }
if min.y == nil { min.y = 0 }
if max.x == nil { max.x = 0 }
if max.y == nil { max.y = 0 }
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
}
fileprivate func scaleValuesOnXAxis(_ values: [Float]) -> [Float] {
let width = Float(drawingWidth)
var factor: Float
if max.x - min.x == 0 {
factor = 0
} else {
factor = width / (max.x - min.x)
}
let scaled = values.map { factor * ($0 - self.min.x) }
return scaled
}
fileprivate func scaleValuesOnYAxis(_ values: [Float]) -> [Float] {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = values.map { Float(self.topInset) + height - factor * ($0 - self.min.y) }
return scaled
}
fileprivate func scaleValueOnYAxis(_ value: Float) -> Float {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = Float(self.topInset) + height - factor * (value - min.y)
return scaled
}
fileprivate func getZeroValueOnYAxis(zeroLevel: Float) -> Float {
if min.y > zeroLevel {
return scaleValueOnYAxis(min.y)
} else {
return scaleValueOnYAxis(zeroLevel)
}
}
fileprivate func drawLine(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let path = CGMutablePath()
path.move(to: CGPoint(x: CGFloat(xValues.first!), y: CGFloat(yValues.first!)))
for i in 1..<yValues.count {
let y = yValues[i]
path.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(y)))
}
let lineLayer = CAShapeLayer()
lineLayer.frame = self.bounds
lineLayer.path = path
if isAboveZeroLine {
lineLayer.strokeColor = series[seriesIndex].colors.above.cgColor
} else {
lineLayer.strokeColor = series[seriesIndex].colors.below.cgColor
}
lineLayer.fillColor = nil
lineLayer.lineWidth = lineWidth
lineLayer.lineJoin = kCALineJoinBevel
self.layer.addSublayer(lineLayer)
layerStore.append(lineLayer)
}
fileprivate func drawArea(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let area = CGMutablePath()
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: series[seriesIndex].colors.zeroLevel))
area.move(to: CGPoint(x: CGFloat(xValues[0]), y: zero))
for i in 0..<xValues.count {
area.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(yValues[i])))
}
area.addLine(to: CGPoint(x: CGFloat(xValues.last!), y: zero))
let areaLayer = CAShapeLayer()
areaLayer.frame = self.bounds
areaLayer.path = area
areaLayer.strokeColor = nil
if isAboveZeroLine {
areaLayer.fillColor = series[seriesIndex].colors.above.withAlphaComponent(areaAlphaComponent).cgColor
} else {
areaLayer.fillColor = series[seriesIndex].colors.below.withAlphaComponent(areaAlphaComponent).cgColor
}
areaLayer.lineWidth = 0
self.layer.addSublayer(areaLayer)
layerStore.append(areaLayer)
}
fileprivate func drawAxes() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(axesColor.cgColor)
context.setLineWidth(0.5)
// horizontal axis at the bottom
context.move(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
// horizontal axis at the top
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.strokePath()
// horizontal axis when y = 0
if min.y < 0 && max.y > 0 {
let y = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: y))
context.strokePath()
}
// vertical axis on the left
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.strokePath()
// vertical axis on the right
context.move(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
}
fileprivate func drawLabelsAndGridOnXAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if xLabels == nil {
// Use labels from the first series
labels = series[0].data.map({ (point: ChartPoint) -> Float in
return point.x })
} else {
labels = xLabels!
}
let scaled = scaleValuesOnXAxis(labels)
let padding: CGFloat = 5
scaled.enumerated().forEach { (i, value) in
let x = CGFloat(value)
let isLastLabel = x == drawingWidth
// Add vertical grid for each label, except axes on the left and right
if x != 0 && x != drawingWidth {
context.move(to: CGPoint(x: x, y: CGFloat(0)))
if xLabelsSkipAll {
let height: CGFloat = bounds.height - 20.0
context.addLine(to: CGPoint(x: x, y: height))
} else {
context.addLine(to: CGPoint(x: x, y: bounds.height))
}
context.strokePath()
}
if (xLabelsSkipLast && isLastLabel) || xLabelsSkipAll {
// Do not add label at the most right position
return
}
// Add label
let label = UILabel(frame: CGRect(x: x, y: drawingHeight, width: 0, height: 0))
label.font = labelFont
label.text = xLabelsFormatter(i, labels[i])
label.textColor = labelColor
// Set label size
label.sizeToFit()
// Center label vertically
label.frame.origin.y += topInset
if xLabelsOrientation == .horizontal {
// Add left padding
label.frame.origin.y -= (label.frame.height - bottomInset) / 2
label.frame.origin.x += padding
// Set label's text alignment
label.frame.size.width = (drawingWidth / CGFloat(labels.count)) - padding * 2
label.textAlignment = xLabelsTextAlignment
} else {
label.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
// Adjust vertical position according to the label's height
label.frame.origin.y += label.frame.size.height / 2
// Adjust horizontal position as the series line
label.frame.origin.x = x
if xLabelsTextAlignment == .center {
// Align horizontally in series
label.frame.origin.x += ((drawingWidth / CGFloat(labels.count)) / 2) - (label.frame.size.width / 2)
} else {
// Give some space from the vertical line
label.frame.origin.x += padding
}
}
self.addSubview(label)
}
}
fileprivate func drawLabelsAndGridOnYAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if yLabels == nil {
labels = [(min.y + max.y) / 2, max.y]
if yLabelsOnRightSide || min.y != 0 {
labels.insert(min.y, at: 0)
}
} else {
labels = yLabels!
}
let scaled = scaleValuesOnYAxis(labels)
let padding: CGFloat = 5
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
scaled.enumerated().forEach { (i, value) in
let y = CGFloat(value)
// Add horizontal grid for each label, but not over axes
if y != drawingHeight + topInset && y != zero {
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: self.bounds.width, y: y))
if labels[i] != 0 {
// Horizontal grid for 0 is not dashed
context.setLineDash(phase: CGFloat(0), lengths: [CGFloat(5)])
} else {
context.setLineDash(phase: CGFloat(0), lengths: [])
}
context.strokePath()
}
let label = UILabel(frame: CGRect(x: padding, y: y, width: 0, height: 0))
label.font = labelFont
label.text = yLabelsFormatter(i, labels[i])
label.textColor = labelColor
label.sizeToFit()
if yLabelsOnRightSide {
label.frame.origin.x = drawingWidth
label.frame.origin.x -= label.frame.width + padding
}
// Labels should be placed above the horizontal grid
label.frame.origin.y -= label.frame.height
self.addSubview(label)
}
UIGraphicsEndImageContext()
}
fileprivate func drawHighlightLineFromLeftPosition(_ left: CGFloat) {
if let shapeLayer = highlightShapeLayer {
// Use line already created
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: 0))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
shapeLayer.path = path
} else {
// Create the line
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: CGFloat(0)))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.path = path
shapeLayer.strokeColor = highlightLineColor.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = highlightLineWidth
highlightShapeLayer = shapeLayer
layer.addSublayer(shapeLayer)
layerStore.append(shapeLayer)
}
}
func handleTouchEvents(_ touches: Set<UITouch>, event: UIEvent!) {
let point = touches.first!
let left = point.location(in: self).x
let x = valueFromPointAtX(left)
if left < 0 || left > (drawingWidth as CGFloat) {
// Remove highlight line at the end of the touch event
if let shapeLayer = highlightShapeLayer {
shapeLayer.path = nil
}
delegate?.didFinishTouchingChart(self)
return
}
drawHighlightLineFromLeftPosition(left)
if delegate == nil {
return
}
var indexes: [Int?] = []
for series in self.series {
var index: Int? = nil
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let closest = Chart.findClosestInValues(xValues, forValue: x)
if closest.lowestIndex != nil && closest.highestIndex != nil {
// Consider valid only values on the right
index = closest.lowestIndex
}
indexes.append(index)
}
delegate!.didTouchChart(self, indexes: indexes, x: x, left: left)
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
delegate?.didEndTouchingChart(self)
handleRangeTouchesEnded(touches, event: event)
}
func handleRangeTouchesEnded(_ touches: Set<UITouch>, event: UIEvent!) {
rightRangePoint = touches.first!
rightRangeLocation = rightRangePoint.location(in: self).x
// Make sure left is actually to the left
if rightRangeLocation < leftRangeLocation {
let rangePoint = leftRangePoint
let rangeLocation = leftRangeLocation
leftRangePoint = rightRangePoint
leftRangeLocation = rightRangeLocation
rightRangePoint = rangePoint
rightRangeLocation = rangeLocation
}
// Highlight the range
let layer = CAShapeLayer()
let width = rightRangeLocation - leftRangeLocation
layer.path = UIBezierPath(rect: CGRect(x: leftRangeLocation, y: topInset, width: width, height: drawingHeight)).cgPath
layer.fillColor = UIColor.red.cgColor
layer.opacity = 0.3
self.layer.addSublayer(layer)
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
}
fileprivate func valueFromPointAtX(_ x: CGFloat) -> Float {
let value = ((max.x-min.x) / Float(drawingWidth)) * Float(x) + min.x
return value
}
fileprivate func valueFromPointAtY(_ y: CGFloat) -> Float {
let value = ((max.y - min.y) / Float(drawingHeight)) * Float(y) + min.y
return -value
}
fileprivate class func findClosestInValues(_ values: [Float],
forValue value: Float
) -> (
lowestValue: Float?,
highestValue: Float?,
lowestIndex: Int?,
highestIndex: Int?
) {
var lowestValue: Float?, highestValue: Float?, lowestIndex: Int?, highestIndex: Int?
values.enumerated().forEach { (i, currentValue) in
if currentValue <= value && (lowestValue == nil || lowestValue! < currentValue) {
lowestValue = currentValue
lowestIndex = i
}
if currentValue >= value && (highestValue == nil || highestValue! > currentValue) {
highestValue = currentValue
highestIndex = i
}
}
return (
lowestValue: lowestValue,
highestValue: highestValue,
lowestIndex: lowestIndex,
highestIndex: highestIndex
)
}
fileprivate class func segmentLine(_ line: ChartLineSegment, zeroLevel: Float) -> [ChartLineSegment] {
var segments: [ChartLineSegment] = []
var segment: ChartLineSegment = []
line.enumerated().forEach { (i, point) in
segment.append(point)
if i < line.count - 1 {
let nextPoint = line[i+1]
if point.y >= zeroLevel && nextPoint.y < zeroLevel || point.y < zeroLevel && nextPoint.y >= zeroLevel {
// The segment intersects zeroLevel, close the segment with the intersection point
let closingPoint = Chart.intersectionWithLevel(point, and: nextPoint, level: zeroLevel)
segment.append(closingPoint)
segments.append(segment)
// Start a new segment
segment = [closingPoint]
}
} else {
// End of the line
segments.append(segment)
}
}
return segments
}
fileprivate class func intersectionWithLevel(_ p1: ChartPoint, and p2: ChartPoint, level: Float) -> ChartPoint {
let dy1 = level - p1.y
let dy2 = level - p2.y
return (x: (p2.x * dy1 - p1.x * dy2) / (dy1 - dy2), y: level)
}
}
I have issues with my side panel for iPad app. I need buttons stacked as below:
Expected Output:
Right now, my output produces:
Current Output:
How can I remove circles and add button sets?
import UIKit
import QuartzCore
public protocol FrostedSidebarDelegate{
func sidebar(sidebar: FrostedSidebar, willShowOnScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, didShowOnScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, willDismissFromScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, didDismissFromScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, didTapItemAtIndex index: Int)
func sidebar(sidebar: FrostedSidebar, didEnable itemEnabled: Bool, itemAtIndex index: Int)
}
var sharedSidebar: FrostedSidebar?
public enum SidebarItemSelectionStyle{
case None
se Single
case All
}
public class FrostedSidebar: UIViewController {
public var width: CGFloat = 300.0
/**
If the sidebar should show from the right.
*/
public var showFromRight: Bool = false
/**
The speed at which the sidebar is presented/dismissed.
*/
public var animationDuration: CGFloat = 0.25
/**
The size of the sidebar items.
*/
public var itemSize: CGSize = CGSize(width: 200.0, height: 200.0)
/**
The background color of the sidebar items.
*/
public var itemBackgroundColor: UIColor = UIColor(white: 1, alpha: 0.25)
/**
The width of the ring around selected sidebar items.
*/
public var borderWidth: CGFloat = 2
/**
The sidebar's delegate.
*/
public var delegate: FrostedSidebarDelegate? = nil
/**
A dictionary that holds the actions for each item index.
*/
public var actionForIndex: [Int : ()->()] = [:]
/**
The indexes that are selected and have rings around them.
*/
public var selectedIndices: NSMutableIndexSet = NSMutableIndexSet()
/**
If the sidebar should be positioned beneath a navigation bar that is on screen.
*/
public var adjustForNavigationBar: Bool = false
/**
Returns whether or not the sidebar is currently being displayed
*/
public var isCurrentlyOpen: Bool = false
/**
The selection style for the sidebar.
*/
public var selectionStyle: SidebarItemSelectionStyle = .None{
didSet{
if case .All = selectionStyle{
selectedIndices = NSMutableIndexSet(indexesInRange: NSRange(location: 0, length: images.count))
}
}
}
//MARK: Private Properties
private var contentView: UIScrollView = UIScrollView()
private var blurView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
private var dimView: UIView = UIView()
private var tapGesture: UITapGestureRecognizer? = nil
private var images: [UIImage] = []
private var borderColors: [UIColor]? = nil
private var itemViews: [CalloutItem] = []
//MARK: Public Methods
/**
Returns an object initialized from data in a given unarchiver.
*/
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
/**
Returns a sidebar initialized with the given data.
- Parameter itemImages: The images that will be used for each item.
- Parameter colors: The color of rings around each image.
- Parameter selectionStyle: The selection style for the sidebar.
- Precondition: `colors` is either `nil` or contains the same number of elements as `itemImages`.
*/
public init(itemImages: [UIImage], colors: [UIColor]?, selectionStyle: SidebarItemSelectionStyle){
contentView.alwaysBounceHorizontal = false
contentView.alwaysBounceVertical = true
contentView.bounces = true
contentView.clipsToBounds = false
contentView.showsHorizontalScrollIndicator = false
contentView.showsVerticalScrollIndicator = false
if let colors = colors{
assert(itemImages.count == colors.count, "If item color are supplied, the itemImages and colors arrays must be of the same size.")
}
self.selectionStyle = selectionStyle
borderColors = colors
images = itemImages
for (index, image) in images.enumerate(){
let view = CalloutItem(index: index)
view.clipsToBounds = true
view.imageView.image = image
contentView.addSubview(view)
itemViews += [view]
if let borderColors = borderColors{
if selectedIndices.containsIndex(index){
let color = borderColors[index]
view.layer.borderColor = color.CGColor
}
} else{
view.layer.borderColor = UIColor.clearColor().CGColor
}
}
super.init(nibName: nil, bundle: nil)
}
public override func shouldAutorotate() -> Bool {
return true
}
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.All
}
public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
if isViewLoaded(){
dismissAnimated(false, completion: nil)
}
}
public override func loadView() {
super.loadView()
view.backgroundColor = UIColor.clearColor()
view.addSubview(dimView)
view.addSubview(blurView)
view.addSubview(contentView)
tapGesture = UITapGestureRecognizer(target: self, action: #selector(FrostedSidebar.handleTap(_:)))
view.addGestureRecognizer(tapGesture!)
}
/**
Shows the sidebar in a view controller.
- Parameter viewController: The view controller in which to show the sidebar.
- Parameter animated: If the sidebar should be animated.
*/
public func showInViewController(viewController: UIViewController, animated: Bool){
layoutItems()
if let bar = sharedSidebar{
bar.dismissAnimated(false, completion: nil)
}
delegate?.sidebar(self, willShowOnScreenAnimated: animated)
sharedSidebar = self
addToParentViewController(viewController, callingAppearanceMethods: true)
view.frame = viewController.view.bounds
dimView.backgroundColor = UIColor.blackColor()
dimView.alpha = 0
dimView.frame = view.bounds
let parentWidth = view.bounds.size.width
var contentFrame = view.bounds
contentFrame.origin.x = showFromRight ? parentWidth : -width
contentFrame.size.width = width
contentView.frame = contentFrame
contentView.contentOffset = CGPoint(x: 0, y: 0)
layoutItems()
var blurFrame = CGRect(x: showFromRight ? view.bounds.size.width : 0, y: 0, width: 0, height: view.bounds.size.height)
blurView.frame = blurFrame
blurView.contentMode = showFromRight ? UIViewContentMode.TopRight : UIViewContentMode.TopLeft
blurView.clipsToBounds = true
view.insertSubview(blurView, belowSubview: contentView)
contentFrame.origin.x = showFromRight ? parentWidth - width : 0
blurFrame.origin.x = contentFrame.origin.x
blurFrame.size.width = width
let animations: () -> () = {
self.contentView.frame = contentFrame
self.blurView.frame = blurFrame
self.dimView.alpha = 0.25
}
let completion: (Bool) -> Void = { finished in
if finished{
self.delegate?.sidebar(self, didShowOnScreenAnimated: animated)
}
}
if animated{
UIView.animateWithDuration(NSTimeInterval(animationDuration), delay: 0, options: UIViewAnimationOptions(), animations: animations, completion: completion)
} else{
animations()
completion(true)
}
for (index, item) in itemViews.enumerate(){
item.layer.transform = CATransform3DMakeScale(0.3, 0.3, 1)
item.alpha = 0
item.originalBackgroundColor = itemBackgroundColor
item.layer.borderWidth = borderWidth
animateSpringWithView(item, idx: index, initDelay: animationDuration)
}
self.isCurrentlyOpen = true
}
/**
Dismisses the sidebar.
- Parameter animated: If the sidebar should be animated.
- Parameter completion: Completion handler called when the sidebar is dismissed.
*/
public func dismissAnimated(animated: Bool, completion: ((Bool) -> Void)?){
let completionBlock: (Bool) -> Void = {finished in
self.removeFromParentViewControllerCallingAppearanceMethods(true)
self.delegate?.sidebar(self, didDismissFromScreenAnimated: true)
self.layoutItems()
if let completion = completion{
completion(finished)
}
}
delegate?.sidebar(self, willDismissFromScreenAnimated: animated)
if animated{
let parentWidth = view.bounds.size.width
var contentFrame = contentView.frame
contentFrame.origin.x = showFromRight ? parentWidth : -width
var blurFrame = blurView.frame
blurFrame.origin.x = showFromRight ? parentWidth : 0
blurFrame.size.width = 0
UIView.animateWithDuration(NSTimeInterval(animationDuration), delay: 0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
self.contentView.frame = contentFrame
self.blurView.frame = blurFrame
self.dimView.alpha = 0
}, completion: completionBlock)
} else{
completionBlock(true)
}
self.isCurrentlyOpen = false
}
/**
Selects the item at the given index.
- Parameter index: The index of the item to select.
*/
public func selectItemAtIndex(index: Int){
let didEnable = !selectedIndices.containsIndex(index)
if let borderColors = borderColors{
let stroke = borderColors[index]
let item = itemViews[index]
if didEnable{
if case .Single = selectionStyle{
selectedIndices.removeAllIndexes()
for item in itemViews{
item.layer.borderColor = UIColor.clearColor().CGColor
}
}
item.layer.borderColor = stroke.CGColor
let borderAnimation = CABasicAnimation(keyPath: "borderColor")
borderAnimation.fromValue = UIColor.clearColor().CGColor
borderAnimation.toValue = stroke.CGColor
borderAnimation.duration = 0.5
item.layer.addAnimation(borderAnimation, forKey: nil)
selectedIndices.addIndex(index)
} else{
if case .None = selectionStyle{
item.layer.borderColor = UIColor.clearColor().CGColor
selectedIndices.removeIndex(index)
}
}
let pathFrame = CGRect(x: -CGRectGetMidX(item.bounds), y: -CGRectGetMidY(item.bounds), width: item.bounds.size.width, height: item.bounds.size.height)
let path = UIBezierPath(roundedRect: pathFrame, cornerRadius: item.layer.cornerRadius)
let shapePosition = view.convertPoint(item.center, fromView: contentView)
let circleShape = CAShapeLayer()
circleShape.path = path.CGPath
circleShape.position = shapePosition
circleShape.fillColor = UIColor.clearColor().CGColor
circleShape.opacity = 0
circleShape.strokeColor = stroke.CGColor
circleShape.lineWidth = borderWidth
view.layer.addSublayer(circleShape)
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
scaleAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeScale(2.5, 2.5, 1))
let alphaAnimation = CABasicAnimation(keyPath: "opacity")
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
let animation = CAAnimationGroup()
animation.animations = [scaleAnimation, alphaAnimation]
animation.duration = 0.5
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
circleShape.addAnimation(animation, forKey: nil)
}
if let action = actionForIndex[index]{
action()
}
delegate?.sidebar(self, didTapItemAtIndex: index)
delegate?.sidebar(self, didEnable: didEnable, itemAtIndex: index)
}
//MARK: Private Classes
private class CalloutItem: UIView{
var imageView: UIImageView = UIImageView()
var itemIndex: Int
var originalBackgroundColor:UIColor? {
didSet{
backgroundColor = originalBackgroundColor
}
}
required init?(coder aDecoder: NSCoder) {
itemIndex = 0
super.init(coder: aDecoder)
}
init(index: Int){
imageView.backgroundColor = UIColor.clearColor()
imageView.contentMode = UIViewContentMode.ScaleAspectFit
itemIndex = index
super.init(frame: CGRect.zero)
addSubview(imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
let inset: CGFloat = bounds.size.height/2
imageView.frame = CGRect(x: 0, y: 0, width: inset, height: inset)
imageView.center = CGPoint(x: inset, y: inset)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
let darkenFactor: CGFloat = 0.3
var darkerColor: UIColor
if originalBackgroundColor != nil && originalBackgroundColor!.getRed(&r, green: &g, blue: &b, alpha: &a){
darkerColor = UIColor(red: max(r - darkenFactor, 0), green: max(g - darkenFactor, 0), blue: max(b - darkenFactor, 0), alpha: a)
} else if originalBackgroundColor != nil && originalBackgroundColor!.getWhite(&r, alpha: &a){
darkerColor = UIColor(white: max(r - darkenFactor, 0), alpha: a)
} else{
darkerColor = UIColor.clearColor()
assert(false, "Item color should be RBG of White/Alpha in order to darken the button")
}
backgroundColor = darkerColor
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
backgroundColor = originalBackgroundColor
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
backgroundColor = originalBackgroundColor
}
}
//MARK: Private Methods
private func animateSpringWithView(view: CalloutItem, idx: Int, initDelay: CGFloat){
let delay: NSTimeInterval = NSTimeInterval(initDelay) + NSTimeInterval(idx) * 0.1
UIView.animateWithDuration(0.5,
delay: delay,
usingSpringWithDamping: 10.0,
initialSpringVelocity: 50.0,
options: UIViewAnimationOptions.BeginFromCurrentState,
animations: {
view.layer.transform = CATransform3DIdentity
view.alpha = 1
},
completion: nil)
}
#objc private func handleTap(recognizer: UITapGestureRecognizer){
let location = recognizer.locationInView(view)
if !CGRectContainsPoint(contentView.frame, location){
dismissAnimated(true, completion: nil)
} else{
let tapIndex = indexOfTap(recognizer.locationInView(contentView))
if let tapIndex = tapIndex{
selectItemAtIndex(tapIndex)
}
}
}
private func layoutSubviews(){
let x = showFromRight ? parentViewController!.view.bounds.size.width - width : 0
contentView.frame = CGRect(x: x, y: 0, width: width, height: parentViewController!.view.bounds.size.height)
blurView.frame = contentView.frame
layoutItems()
}
private func layoutItems(){
let leftPadding: CGFloat = (width - itemSize.width) / 2
let topPadding: CGFloat = leftPadding
for (index, item) in itemViews.enumerate(){
let idx: CGFloat = adjustForNavigationBar ? CGFloat(index) + 0.5 : CGFloat(index)
let frame = CGRect(x: leftPadding, y: topPadding*idx + itemSize.height*idx + topPadding, width:itemSize.width, height: itemSize.height)
item.frame = frame
item.layer.cornerRadius = frame.size.width / 2
item.layer.borderColor = UIColor.clearColor().CGColor
item.alpha = 0
if selectedIndices.containsIndex(index){
if let borderColors = borderColors{
item.layer.borderColor = borderColors[index].CGColor
}
}
}
let itemCount = CGFloat(itemViews.count)
if adjustForNavigationBar{
contentView.contentSize = CGSizeMake(0, (itemCount + 0.5) * (itemSize.height + topPadding) + topPadding)
} else {
contentView.contentSize = CGSizeMake(0, itemCount * (itemSize.height + topPadding) + topPadding)
}
}
private func indexOfTap(location: CGPoint) -> Int? {
var index: Int?
for (idx, item) in itemViews.enumerate(){
if CGRectContainsPoint(item.frame, location){
index = idx
break
}
}
return index
}
private func addToParentViewController(viewController: UIViewController, callingAppearanceMethods: Bool){
if let _ = parentViewController{
removeFromParentViewControllerCallingAppearanceMethods(callingAppearanceMethods)
}
if callingAppearanceMethods{
beginAppearanceTransition(true, animated: false)
}
viewController.addChildViewController(self)
viewController.view.addSubview(view)
didMoveToParentViewController(self)
if callingAppearanceMethods{
endAppearanceTransition()
}
}
private func removeFromParentViewControllerCallingAppearanceMethods(callAppearanceMethods: Bool){
if callAppearanceMethods{
beginAppearanceTransition(false, animated: false)
}
willMoveToParentViewController(nil)
view.removeFromSuperview()
removeFromParentViewController()
if callAppearanceMethods{
endAppearanceTransition()
}
}
}
Instead of putting each button in it's own view, you need to create a view that contains 3 buttons and then add the circle to the view.
I have tested the following code on all simulators and it works fine except on the iPad Pro. On the the iPad Pro is does not display. The gradient layer will work fine, it is only when I try and apply a mask that it will not appear on the iPad Pro Simulator:
func createOverlay()
{
if !(gradientLayer != nil)
{
self.gradientLayer = CAGradientLayer()
self.layer.addSublayer(gradientLayer)
}
gradientLayer.frame = self.bounds
print(gradientLayer.frame)
gradientLayer.colors = [appColour.CGColor, appColourDark.CGColor]
//--------FROM HERE ON DOES NOT WORK ON IPAD PRO, NO CRASH BUT LAYER DOES NOT APPEAR
self.alpha = maskAlpha
let maskLayer = CAShapeLayer()
let path = CGPathCreateMutable()
let rect: CGRect = CGRect(x: xOffset - offset, y: yOffset - offset, width: circleWidth + (offset * 2), height: circleHeight + (offset * 2))
let bPath = UIBezierPath(ovalInRect: rect)
CGPathAddRect(path, nil, CGRectMake(0, 0, self.frame.width, self.frame.height))
CGPathAddPath(path, nil, bPath.CGPath)
maskLayer.backgroundColor = UIColor.blackColor().CGColor
maskLayer.path = path
maskLayer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = maskLayer
self.clipsToBounds = true
}
I am hoping this is just a simulator issue but if you see something in my code that might be causing a problem please let me know.
I have tried replacing the gradient layer with a normal layer but it still does not display.
Here is full code, it is a custom sub-class of UIView and is the top layer of a view controller setup in IB:
import UIKit
protocol TipSpeechDelegate
{
func stopSpeaking()
}
#IBDesignable
class HoleMaskView: UIView
{
var xOffset : CGFloat = 0.0
var yOffset : CGFloat = 0.0
var circleWidth: CGFloat = 0.0
var circleHeight: CGFloat = 0.0
var maskAlpha: CGFloat = 0.9
var offset: CGFloat = 10.0
var inset: CGFloat = 8.0
var tipText: String = ""
var myLabel: UILabel?
var gradientLayer: CAGradientLayer!
var relativeCorner: RelativeCornerType = RelativeCornerType.upperLeftCorner
var delegate: TipSpeechDelegate!
override func layoutSubviews()
{
super.layoutSubviews()
}
override func drawRect(rect: CGRect)
{
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(HoleMaskView.handleTap(_:)))
self.addGestureRecognizer(gestureRecognizer)
}
func drawTipText()
{
createOverlay()
let height: CGFloat = self.frame.size.height
if (self.myLabel != nil)
{
myLabel?.removeFromSuperview()
}
if (yOffset < height/2)
{
self.myLabel = UILabel(frame: CGRect(x: inset, y: (yOffset + circleHeight), width: self.frame.width-(inset*2), height: self.frame.height-(yOffset + circleHeight)))
}
else
{
self.myLabel = UILabel(frame: CGRect(x: inset, y: 0, width: self.frame.width-(inset*2), height: yOffset))
}
self.myLabel?.text = tipText
self.myLabel?.textColor = UIColor.whiteColor()
self.myLabel?.font = UIFont(name: "Avenir-Medium", size: 20.0)
self.myLabel?.textAlignment = .Center
self.myLabel?.lineBreakMode = .ByWordWrapping
self.myLabel?.numberOfLines = 0
self.myLabel?.setNeedsLayout()
self.addSubview(myLabel!)
}
func updateTipText(text: String, circle: CGRect)
{
self.tipText = text
yOffset = circle.origin.y
xOffset = circle.origin.x
circleWidth = circle.size.width
circleHeight = circle.size.height
self.drawTipText()
}
func tipText(text: String, rFrame: CGRect, inView: UIView) -> Bool
{
showTipMask()
let convertedPoint = inView.convertPoint(rFrame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = rFrame.size.width
circleHeight = rFrame.size.height
self.drawTipText()
return true
}
func tipText(text: String, button: UIButton) -> Bool
{
if button.hidden
{
return false
}
showTipMask()
let convertedPoint = button.superview!.convertPoint(button.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = button.frame.size.width
circleHeight = button.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String, label: UILabel) -> Bool
{
if label.hidden
{
return false
}
showTipMask()
let convertedPoint = label.superview!.convertPoint(label.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = label.frame.size.width
circleHeight = label.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String, textView: UITextView) -> Bool
{
if textView.hidden
{
return false
}
showTipMask()
let convertedPoint = textView.superview!.convertPoint(textView.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = textView.frame.size.width
circleHeight = textView.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String) -> Bool
{
showTipMask()
self.tipText = text
yOffset = 0.0
xOffset = self.frame.size.width/2
circleWidth = 0.0
circleHeight = 0.0
self.drawTipText()
return true
}
func tipText(text: String, view: UIView) -> Bool
{
if view.hidden
{
return false
}
showTipMask()
let convertedPoint = view.superview!.convertPoint(view.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = view.frame.size.width
circleHeight = view.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String, stepper: UIStepper) -> Bool
{
if stepper.hidden
{
return false
}
showTipMask()
let convertedPoint = stepper.superview!.convertPoint(stepper.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = stepper.frame.size.width
circleHeight = stepper.frame.size.height
self.drawTipText()
return true
}
func showTipMask()
{
self.alpha = alphaHide
self.hidden = false
UIView.animateWithDuration(0.5, animations:
{
self.alpha = alphaShow
}
)
}
func handleTap(gestureRecognizer: UIGestureRecognizer)
{
if delegate != nil
{
delegate.stopSpeaking()
}
print("tapped internal")
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.TransitionNone, animations:
{
() -> Void in
self.alpha = alphaHide
},
completion:
{
(finished: Bool) -> Void in
self.hidden = true
}
)
}
func createOverlay()
{
if !(gradientLayer != nil)
{
self.gradientLayer = CAGradientLayer()
self.layer.addSublayer(gradientLayer)
}
gradientLayer.frame = self.bounds
print(gradientLayer.frame)
gradientLayer.colors = [appColour.CGColor, appColourDark.CGColor]
self.alpha = maskAlpha
let maskLayer = CAShapeLayer()
let path = CGPathCreateMutable()
let rect: CGRect = CGRect(x: xOffset - offset, y: yOffset - offset, width: circleWidth + (offset * 2), height: circleHeight + (offset * 2))
let bPath = UIBezierPath(ovalInRect: rect)
CGPathAddRect(path, nil, CGRectMake(0, 0, self.frame.width, self.frame.height))
CGPathAddPath(path, nil, bPath.CGPath)
maskLayer.backgroundColor = UIColor.blackColor().CGColor
maskLayer.path = path
maskLayer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = maskLayer
self.clipsToBounds = true
}
}
Thanks
Greg
I am facing the same issue.
Currently I
turn on the Debug -> Optimize Rendering for Window Scale
scale it down to 50%
then the graph appears correctly.
But one of my colleges told me that he has to turn off the ORWS option. Wired.
i am brand new to swift and i am trying to program a pacman. i am trying to move the pacman to the direction of the swipe, so far i have managed to move it to the edges of the screen, the problem is that when i try to move it not from the edge of the screen but in the middle of the swipe action, it just goes to the edge of the screen and moves to the swipe direction, here is the code for one direction:
var x = view.center.x
for var i = x; i > 17; i--
{
var origin: CGPoint = self.view.center
var move = CABasicAnimation(keyPath:"position.x")
move.speed = 0.13
move.fromValue = NSValue(nonretainedObject: view.center.x)
move.toValue = NSValue(nonretainedObject: i)
view.layer.addAnimation(move, forKey: "position")
view.center.x = i
}
the thing is that i know the problem which is when i swipe to the direction that i want the for loop will not wait for the animation to stop but it will finish the loop in less than a second and i need sort of delay here or other code.
This was an interesting question, so I decided to make an example in SpriteKit. There isn't any collision detection, path finding or indeed even paths. It is merely an example of how to make 'Pac-Man' change direction when a swipe occurs.
I have included the GameScene below:
class GameScene: SKScene {
enum Direction {
case Left
case Right
case Up
case Down
}
lazy var openDirectionPaths = [Direction: UIBezierPath]()
lazy var closedDirectionPaths = [Direction: UIBezierPath]()
lazy var wasClosedPath = false
lazy var needsToUpdateDirection = false
lazy var direction = Direction.Right
lazy var lastChange: NSTimeInterval = NSDate().timeIntervalSince1970
var touchBeganPoint: CGPoint?
let pacmanSprite = SKShapeNode(circleOfRadius: 15)
override func didMoveToView(view: SKView) {
let radius: CGFloat = 15, diameter: CGFloat = 30, center = CGPoint(x:radius, y:radius)
func createPaths(startDegrees: CGFloat, endDegrees: CGFloat, inout dictionary dic: [Direction: UIBezierPath]) {
var path = UIBezierPath(arcCenter: center, radius: radius, startAngle: startDegrees.toRadians(), endAngle: endDegrees.toRadians(), clockwise: true)
path.addLineToPoint(center)
path.closePath()
dic[.Right] = path
for d: Direction in [.Up, .Left, .Down] {
path = path.pathByRotating(90)
dic[d] = path
}
}
createPaths(35, 315, dictionary: &openDirectionPaths)
createPaths(1, 359, dictionary: &closedDirectionPaths)
pacmanSprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
pacmanSprite.fillColor = UIColor.yellowColor()
pacmanSprite.lineWidth = 2
if let path = openDirectionPaths[.Right] {
pacmanSprite.path = path.CGPath
}
pacmanSprite.strokeColor = UIColor.blackColor()
self.addChild(pacmanSprite)
updateDirection()
// Blocks to stop 'Pacman' changing direction outside of a defined path?
//375/25 = 15 width
//666/37 = 18 height
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
touchBeganPoint = positionOfTouch(inTouches: touches)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touchStartPoint = touchBeganPoint,
touchEndPoint = positionOfTouch(inTouches: touches) {
if touchStartPoint == touchEndPoint {
return
}
let degrees = atan2(touchStartPoint.x - touchEndPoint.x,
touchStartPoint.y - touchEndPoint.y).toDegrees()
var oldDirection = direction
switch Int(degrees) {
case -135...(-45): direction = .Right
case -45...45: direction = .Down
case 45...135: direction = .Left
default: direction = .Up
}
if (oldDirection != direction) {
needsToUpdateDirection = true
}
}
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
touchBeganPoint = nil
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if let nodes = self.children as? [SKShapeNode] {
for node in nodes {
let p = node.position
let s = node.frame.size
//let s = node.size
if p.x - s.width > self.size.width {
node.position.x = -s.width
}
if p.y - s.height > self.size.height {
node.position.y = -s.height
}
if p.x < -s.width {
node.position.x = self.size.width + (s.width / 2)
}
if p.y < -s.height {
node.position.y = self.size.height + (s.height / 2)
}
if needsToUpdateDirection || NSDate().timeIntervalSince1970 - lastChange > 0.25 {
if let path = wasClosedPath ? openDirectionPaths[direction]?.CGPath : closedDirectionPaths[direction]?.CGPath {
node.path = path
}
wasClosedPath = !wasClosedPath
lastChange = NSDate().timeIntervalSince1970
}
updateDirection()
}
}
}
// MARK:- Helpers
func positionOfTouch(inTouches touches: Set<NSObject>) -> CGPoint? {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
return location
}
return nil
}
func updateDirection() {
if !needsToUpdateDirection {
return
}
pacmanSprite.removeActionForKey("Move")
func actionForDirection() -> SKAction {
let Delta: CGFloat = 25
switch (direction) {
case .Up:
return SKAction.moveByX(0.0, y: Delta, duration: 0.1)
case .Down:
return SKAction.moveByX(0.0, y: -Delta, duration: 0.1)
case .Right:
return SKAction.moveByX(Delta, y: 0.0, duration: 0.1)
default:
return SKAction.moveByX(-Delta, y: 0.0, duration: 0.1)
}
}
let action = SKAction.repeatActionForever(actionForDirection())
pacmanSprite.runAction(action, withKey: "Move")
needsToUpdateDirection = false
}
}
The repository can be found here
I have added the MIT license, so you can fork this repository if you wish. I hope this helps.