I want to create programatically Hexagon UIButtons with Swift. I have hexagon images as you can see green picture. The edges are transparent, so you shouldn't click that areas. By using default UIButton with "custom" option, these areas are still clickable.
Then add them like image below on UIScrollView. The main thing is that when user clicks one button I should get the correct button's id.
So, can you help me with creating hexagon UIButton or clickable image view? Thanks.
You can extend the UIButton class to not detect touch events at the parts of image that are transparent.
We detect the alpha component of the image at the touch point.
If the alpha component is 0, the hit test will fail.
If the alpha is non zero, hit test will succeed.
The hit test will also fail if the touch is outside the button bounds.
extension UIButton {
func getColourFromPoint(point:CGPoint) -> UIColor {
let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
var pixelData:[UInt8] = [0, 0, 0, 0]
let context = CGBitmapContextCreate(&pixelData, 1, 1, 8, 4, colorSpace, bitmapInfo)
CGContextTranslateCTM(context, -point.x, -point.y);
self.layer.renderInContext(context)
var red:CGFloat = CGFloat(pixelData[0])/CGFloat(255.0)
var green:CGFloat = CGFloat(pixelData[1])/CGFloat(255.0)
var blue:CGFloat = CGFloat(pixelData[2])/CGFloat(255.0)
var alpha:CGFloat = CGFloat(pixelData[3])/CGFloat(255.0)
var color:UIColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
return color
}
public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if (!CGRectContainsPoint(self.bounds, point)) {
return nil
}
else {
let color : UIColor = self.getColourFromPoint(point)
let alpha = CGColorGetAlpha(color.CGColor);
if alpha <= 0.0 {
return nil
}
return self
}
}
}
Related
I am try create effect like few view stack over other for collection view, I am not build effect like this in the past and can't understand how I can create this effect programmatically without images?
Can somebody explain me how I can repeat this effect or post link to code of tutorial?
Example of this effect below:
To get that effect you'd need to add layers under the collection view cells' layers that are smaller and shifted down a little from the cell's layers. You'd use the same background color as the cell on each layer that had a lower alpha than the cell's layer.
I created a sample project that demonstrates how to get the effect:
https://github.com/DuncanMC/CustomCollectionViewCell.git
The cells look like this:
The heavy lifting is in a custom subclass of UICollectionViewCell that I called MyCollectionViewCell
Here is that class:
class MyCollectionViewCell: UICollectionViewCell {
public var contentCornerRadius: CGFloat = 0 {
didSet {
contentView.layer.cornerRadius = contentCornerRadius
sizeLayerFrames()
}
}
public var fraction: CGFloat = 0.075 { // What percent to shrink & shift the faded layers down (0.075 = 7.5%)
didSet {
sizeLayerFrames()
}
}
private var layerMask = CAShapeLayer()
private var layer1 = CALayer()
private var layer2 = CALayer()
// Use this function to set the cell's background color.
// (You can't set the view's background color, since we Don't clip the view to it's bounds.)
// Be sure to set the background color explicitly, since by default it sets a random color that will persist
// as the cells are recylced, causing your cell colors to move around as the user scrolls
public func setBackgroundColor(_ color: UIColor) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
contentView.layer.backgroundColor = color.cgColor
//Make the first extra layer have the same color as the cell's layer, but with alpha 0.25
layer1.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.25).cgColor
//Make the second extra layer have the same color as the cell's layer, but with alpha 0.125
layer2.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.125).cgColor
}
#IBOutlet weak var customLabel: UILabel!
//Do The initial setup once the cell is loaded.
//Note that t
override func awakeFromNib() {
contentView.layer.masksToBounds = false
// Color each cell's layer some random hue (change to set whatever color you desire.)
//For testing, use a color based on a random hue and fairly high random brightness.
let hue = CGFloat.random(in: 0...360)
let brightness = CGFloat.random(in: 0.8...1.0)
layer.masksToBounds = false
setBackgroundColor(UIColor(hue: hue, saturation: 1, brightness: brightness, alpha: 1))
// Make the inside of the shape layer white (opaque), The color doesn't matter - just that the alpha value is 1
// and the outside clear (transparent)
layerMask.fillColor = UIColor.white.cgColor
layerMask.backgroundColor = UIColor.clear.cgColor
//With the even/odd rule, the inner shape will not be filled (we'll only fill the part NOT in the inner shape)
layerMask.fillRule = .evenOdd
contentCornerRadius = 30
sizeLayerFrames()
contentView.layer.addSublayer(layer1)
layer1.addSublayer(layer2)
layer1.mask = layerMask
}
private func sizeLayerFrames() {
layer1.cornerRadius = contentCornerRadius
layer2.cornerRadius = contentCornerRadius
let viewBounds = bounds //Use the layer's bounds as the starting point for the extra layers.
var frame1 = viewBounds
frame1.origin.y += viewBounds.size.height * fraction
frame1.origin.x += viewBounds.size.width * fraction
frame1.size.width *= CGFloat(1 - 2 * fraction)
layer1.frame = frame1
var frame2 = viewBounds
frame2.origin.y += viewBounds.size.height * 0.75 * fraction
frame2.origin.x += viewBounds.size.width * fraction
frame2.size.width *= CGFloat(1 - 4 * fraction)
layer2.frame = frame2
//Create a mask layer to clip the extra layers.
var maskFrame = viewBounds
//We are going to install the mask on layer1, so offeset the frame to cover the whole view contents
maskFrame.origin.y -= viewBounds.size.height * fraction
maskFrame.origin.x -= viewBounds.size.width * fraction
maskFrame.size.height += viewBounds.size.height * fraction * 1.75
layerMask.frame = maskFrame
maskFrame = viewBounds
let innerPath = UIBezierPath(roundedRect: maskFrame, cornerRadius: 30)
maskFrame.size.height += viewBounds.size.height * fraction * 1.75
let combinedPath = UIBezierPath(rect: maskFrame)
combinedPath.append(innerPath)
layerMask.path = combinedPath.cgPath
}
override var bounds: CGRect {
didSet {
sizeLayerFrames()
}
}
}
This is my first question, so please excuse me if I'm unclear in any way.
I would like to be able to create a new UIImage2 from the contents of a freehand drawn shape over an existing UIImage1 to display over the existing UIImage1.
A visual example: UIImage1 is a photo of a city skyline. The user should be able to draw an outline around a skyscraper and create a new UIImage2 which will be displayed stacked over UIImage1, with the ability to edit (size, color, etc) of UIImage 2.
Drawing lines over a UIImage isn't too difficult, but I've yet to figure out how to capture the content from the UIImage within that freehand drawn shape creating a separate UIImage.
I'd greatly appreciate any help.
If you want to create an image from a masked path, you can:
Create UIBezierPath from user gesture;
Use that bezier path as the CAShapeLayer on the image view;
When the user gesture is done, remove that shape layer, but create new shape layer and use it as a mask; and
Create image from that masked image view using UIGraphicsImageRenderer and drawHierarchy to render whatever should be captured in the image.
For example:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private var path: UIBezierPath?
private var strokeLayer: CAShapeLayer?
#IBAction func didHandlePan(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: imageView)
switch gesture.state {
case .began:
path = UIBezierPath()
path?.move(to: location)
strokeLayer = CAShapeLayer()
imageView.layer.addSublayer(strokeLayer!)
strokeLayer?.strokeColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1).cgColor
strokeLayer?.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
strokeLayer?.lineWidth = 5
strokeLayer?.path = path?.cgPath
case .changed:
path?.addLine(to: location)
strokeLayer?.path = path?.cgPath
case .cancelled, .ended:
// remove stroke from image view
strokeLayer?.removeFromSuperlayer()
strokeLayer = nil
// mask the image view
let mask = CAShapeLayer()
mask.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
mask.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
mask.lineWidth = 0
mask.path = path?.cgPath
imageView.layer.mask = mask
// get cropped image
let image = imageView?.snapshot
imageView.layer.mask = nil
// perhaps use that image?
imageView.image = image
default: break
}
}
}
By the way, to create UIImage from a view (masked or otherwise), you can use:
extension UIView {
var snapshot: UIImage {
UIGraphicsImageRenderer(bounds: bounds).image { _ in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
}
That yields something like:
I have a SpriteKit scene with a sprite. The sprite has a physics body derived from the texture's alpha to get an accurate physics shape like so:
let texture_bottle = SKTexture(imageNamed:"Bottle")
let sprite_bottle = SKSpriteNode(texture: texture_bottle)
physicsBody_bottle = SKPhysicsBody(texture: texture_bottle, size: size)
physicsBody_bottle.affectedByGravity = false
sprite_bottle.physicsBody = physicsBody_bottle
root.addChild(sprite_bottle)
....
func touchesBegan(_ touches: Set<UITouch>?, with event: UIEvent?, touchLocation: CGPoint!) {
let hitNodes = self.nodes(at: touchLocation)
}
When a user taps the screen, how can I detect if they actually touched within the physics body shape (not the sprite's rect)?
You "can't" (Not easily)
UITouch commands are based on CGRects, so let hitNodes = self.nodes(at: touchLocation) is going to be filled with any node who's frame intersects with that touch.
This can't be avoided, so the next step is to determine pixel accuracy from the nodes that registered as "hit". The first thing you should do is convert the touch position to local coordinates to your sprite.
for node in hitNodes
{
//assuming touchLocation is based on screen coordinates
let localLocation = node.convertPoint(touchLocation,from:scene)
}
Then from this point you need to figure out which method you want to use.
If you need speed, then I would recommend creating a 2D boolean array that behaves as a mask, and fill this array with false for transparent areas and true for opaque areas. Then you can use localLocation to point to a certain index of the array (Remember to add anchorPoint * width and height to your x and y values then cast to int)
func isHit(node: SKNode,mask: [[Boolean]],position:CGPoint) -> Boolean
{
return mask[Int(node.size.height * node.anchorPoint.y + position.y)][Int(node.size.width * node.anchorPoint.x + position.x)]
}
If speed is not of concern, then you can create a CGContext, fill your texture into this context, and then check if the point in the context is transparent or not.
Something like this would help you out:
How do I get the RGB Value of a pixel using CGContext?
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground
extension CALayer {
func colorOfPoint(point:CGPoint) -> UIColor
{
var pixel:[CUnsignedChar] = [0,0,0,0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)
let context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace,bitmapInfo.rawValue)
CGContextTranslateCTM(context, -point.x, -point.y)
self.renderInContext(context!)
let red:CGFloat = CGFloat(pixel[0])/255.0
let green:CGFloat = CGFloat(pixel[1])/255.0
let blue:CGFloat = CGFloat(pixel[2])/255.0
let alpha:CGFloat = CGFloat(pixel[3])/255.0
//println("point color - red:\(red) green:\(green) blue:\(blue)")
let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)
return color
}
}
extension UIColor {
var components:(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
var r:CGFloat = 0
var g:CGFloat = 0
var b:CGFloat = 0
var a:CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r,g,b,a)
}
}
//get an image we can work on
var imageFromURL = UIImage(data: NSData(contentsOfURL: NSURL(string:"https://www.gravatar.com/avatar/ba4178644a33a51e928ffd820269347c?s=328&d=identicon&r=PG&f=1")!)!)
//only use a small area of that image - 50 x 50 square
let imageSliceArea = CGRectMake(0, 0, 50, 50);
let imageSlice = CGImageCreateWithImageInRect(imageFromURL?.CGImage, imageSliceArea);
//we'll work on this image
var image = UIImage(CGImage: imageSlice!)
let imageView = UIImageView(image: image)
//test out the extension above on the point (0,0) - returns r 0.541 g 0.78 b 0.227 a 1.0
var pointColor = imageView.layer.colorOfPoint(CGPoint(x: 0, y: 0))
let imageRect = CGRectMake(0, 0, image.size.width, image.size.height)
UIGraphicsBeginImageContext(image.size)
let context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context)
CGContextDrawImage(context, imageRect, image.CGImage)
for x in 0...Int(image.size.width) {
for y in 0...Int(image.size.height) {
var pointColor = imageView.layer.colorOfPoint(CGPoint(x: x, y: y))
//I used my own creativity here - change this to whatever logic you want
if y % 2 == 0 {
CGContextSetRGBFillColor(context, pointColor.components.red , 0.5, 0.5, 1)
}
else {
CGContextSetRGBFillColor(context, 255, 0.5, 0.5, 1)
}
CGContextFillRect(context, CGRectMake(CGFloat(x), CGFloat(y), 1, 1))
}
}
CGContextRestoreGState(context)
image = UIGraphicsGetImageFromCurrentImageContext()
where you would eventually call colorOfPoint(point:localLocation).cgColor.alpha > 0 to determine if you are touching a node or not.
Now I would recommend you make colorOfPoint an extension of SKSpriteNode, so be creative with the code posted above.
func isHit(node: SKSpriteNode,position:CGPoint) -> Boolean
{
return node.colorOfPoint(point:localLocation).cgColor.alpha > 0
}
Your final code would look something like this:
hitNodes = hitNodes.filter
{
node in
//assuming touchLocation is based on screen coordinates
let localLocation = node.convertPoint(touchLocation,from:node.scene)
return isHit(node:node,mask:mask,position:localLocation)
}
OR
hitNodes = hitNodes.filter
{
node in
//assuming touchLocation is based on screen coordinates
let localLocation = node.convertPoint(touchLocation,from:node.scene)
return isHit(node:node,position:localLocation)
}
which is basically filtering out all nodes that were in the detected by the frame comparison, leaving you pixel perfect touched nodes.
Note: The code from the separate SO link may need to be converted to Swift 4.
I use an NSTableView to draw some information in NSTableCellView(s). I want to set the background color of the NSTableView to a certain value and the NSTableCellView's background color to another value independently of the alpha component of the colors used. The problem is that if I set a background color with alpha component 0.3 to NSTableCellView, we see the background color of the NSTableView and then the color is not what I set.
I see two options to solve this problem:
draw the background color of the NSTableView without drawing under the rects used by the NSTableCellView(s).
use color theory and CoreGraphics to compute the new color.
I have worked around a bit option 1 and haven't got any result. I am now looking more into option 2.
For example, if I have two colors:
let tableViewBackgroundColor = NSColor(calibratedRed: 48/255, green: 47/255, blue: 46/255, alpha: 1)
let tableViewCellBackgroundColor = NSColor(calibratedRed: 42/255, green: 41/255, blue: 40/255, alpha: 1)
I want, that the resulting color applied to NSTableCellView background:
let targetColor = tableViewCellBackgroundColor.withAplphaComponent(0.3)
even when the color:
let tableViewBackgroundColorWithAlpha = tableViewBackgroundColor.withAlphaComponent(0.3)
is applied to the background of the NSTableView.
I am looking for an extension to NSColor (CGColor would work) like this:
extension NSColor {
///
/// Return the color that needs to be composed with the color parameter
/// in order to result in the current (self) color.
///
func composedColor(with color: NSColor) -> NSColor
}
That could be used like this:
let color = targetColor.composedColor(with:
tableViewBackgroundColorWithAlpha)
Any idea?
Answering my own question, the solution for this problem has finally been to avoid drawing the parts with table view cells (option 1 on the question). I inspired myself from Drawing Custom Alternating Row Backgrounds in NSTableViews with Swift to implement the final solution.
It basically involve overriding the method func drawBackground(inClipRect clipRect: NSRect) of the NSTableView but avoiding drawing the background part of the table where there is cells present.
Here is the solution:
override func drawBackground(inClipRect clipRect: NSRect) {
super.drawBackground(inClipRect: clipRect)
drawTopBackground(inClipRect: clipRect)
drawBottomBackground(inClipRect: clipRect)
}
private func drawTopBackground(inClipRect clipRect: NSRect) {
guard clipRect.origin.y < 0 else { return }
let rectHeight = rowHeight + intercellSpacing.height
let minY = NSMinY(clipRect)
var row = 0
currentBackgroundColor.setFill()
while true {
let rowRect = NSRect(x: 0, y: (rectHeight * CGFloat(row) - rectHeight), width: NSMaxX(clipRect), height: rectHeight)
if self.rows(in: rowRect).isEmpty {
rowRect.fill()
}
if rowRect.origin.y < minY { break }
row -= 1
}
}
private func drawBottomBackground(inClipRect clipRect: NSRect) {
let rectHeight = rowHeight + intercellSpacing.height
let maxY = NSMaxY(clipRect)
var row = rows(in: clipRect).location
currentBackgroundColor.setFill()
while true {
let rowRect = NSRect(
x: 0,
y: (rectHeight * CGFloat(row)),
width: NSMaxX(clipRect),
height: rectHeight)
if self.rows(in: rowRect).isEmpty {
rowRect.fill()
}
if rowRect.origin.y > maxY { break }
row += 1
}
}
I have an Icon drawn via PaintCode. I need to change his color in XCode, programmatically.
On PaintCode, I set a Variable "ChevronColor" to the Stroke Color.
For now I have:
#IBDesignable
class IconClass: UIView {
override func draw(_ rect: CGRect) {
StyleKit.drawIcon(frame: self.bounds, resizing: .aspectFit)
}
}
But I would like to add kind of this, to be able to set the color of the icon.
#IBInspectable
var ChevronColor : UIColor {
didSet (newColor) {
setNeedsDisplay()
}
}
I don't know how to do.
After exporting the StyleKit file, I excepted to have this method available in the stylekit file, but it's not the case:
StyleKit.drawIcon(frame: self.bounds, resizing: .aspectFit, chevronColor: self.ChevronColor)
TL/DR
Create an expression that takes red, green, blue, alpha (external parameters in PaintCode) and generates a color (makeColor function in PaintCode). The generated color is then assigned to the Stroke, Fill, whatever, via that expression.
PaintCode
Custom View
import Foundation
import UIKit
#IBDesignable class TreeView: UIView {
/*
*
* Notice - supported range for colors and alpha: 0 to 1.
* Color 0.808, 0.808, 0.808 = gray (starting color in this example).
*
*/
#IBInspectable var redColor: CGFloat = 0.808 {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var greenColor: CGFloat = 0.808 {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var blueColor: CGFloat = 0.808 {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var alphaColor: CGFloat = 1 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
StyleKit.drawTreeIcon(frame: rect,
resizing: .aspectFit,
red: redColor,
green: greenColor,
blue: blueColor,
alpha: alphaColor)
}
}
Changing color example
#IBAction func colorButtonPressed(_ sender: UIButton) {
// Get color references.
let red = CIColor(color: sender.backgroundColor!).red
let green = CIColor(color: sender.backgroundColor!).green
let blue = CIColor(color: sender.backgroundColor!).blue
let alpha = CIColor(color: sender.backgroundColor!).alpha
// Update the PaintCode generated icon.
treeView.redColor = red
treeView.greenColor = green
treeView.blueColor = blue
treeView.alpha = alpha
}
Demo
Reference
The project can be cloned from my GitHub repo.
Also take a look at the PaintCode Expression Language guide.
Just edit color in Colors section access level to Parameter.