Draw a separator line in conjunction with Auto Layout - ios

I'm reimplementig some kind of UISplitViewController. Now I need to draw a separator line between the master and the detail view. Now I have some questions for this:
Should the separator line be on the view controller itself or should it be a separate view?
What about Auto Layout? Setting a frame is not allowed.
I saw solutions for CALayer/CAShapeLayer, drawRect (CoreGraphics) or using the background color of an UIView/UILabel. The last one should cost too much performance according to the folks there.
On the one side it is comfortable to draw the line in the UITableViewController itself. Or should a separate UIView be created? If I embed a separate UIView there will be much more constraints and it will complicate things (would have to set separate widths) ... Also it should adapt to orientation changes (e.g. the size of the UITableViewController changes -> the separation line should also resize).
How can I add such a dividing rule? Such a dividing rule can be seen here:

If you need to add a true one pixel line, don't fool with an image. It's almost impossible. Just use this:
#interface UILine : UIView
#end
#implementation UILine
- (void)awakeFromNib {
CGFloat sortaPixel = 1 / [UIScreen mainScreen].scale;
// recall, the following...
// CGFloat sortaPixel = 1 / self.contentScaleFactor;
// ...does NOT work when loading from storyboard!
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, sortaPixel)];
line.userInteractionEnabled = NO;
line.backgroundColor = self.backgroundColor;
line.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self addSubview:line];
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = NO;
}
#end
How to use:
Actually in storyboard, simply make a UIView that is in the exact place, and exact width, you want. (Feel free to use constraints/autolayout as normal.)
Make the view say five pixels high, simply so you can see it clearly, while working.
Make the top of the UIView exactly where you want the single-pixel line. Make the UIView the desired color of the line.
Change the class to UILine. At run time, it will draw a perfect single-pixel line in the exact location on all devices.
(For a vertical line class, simply modify the CGRectMake.)
Hope it helps!

I took #joe-blow's excellent answer a step further and created a view that is not only rendered in IB but also whose line width and line color can be changed via the inspector in IB. Simply add a UIView to your storyboard, size appropriately, and change the class to LineView.
import UIKit
#IBDesignable
class LineView: UIView {
#IBInspectable var lineWidth: CGFloat = 1.0
#IBInspectable var lineColor: UIColor? {
didSet {
lineCGColor = lineColor?.CGColor
}
}
var lineCGColor: CGColorRef?
override func drawRect(rect: CGRect) {
// Draw a line from the left to the right at the midpoint of the view's rect height.
let midpoint = self.bounds.size.height / 2.0
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, lineWidth)
if let lineCGColor = self.lineCGColor {
CGContextSetStrokeColorWithColor(context, lineCGColor)
}
else {
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
}
CGContextMoveToPoint(context, 0.0, midpoint)
CGContextAddLineToPoint(context, self.bounds.size.width, midpoint)
CGContextStrokePath(context)
}
}

Updating the #mharper answer to Swift 3.x
import UIKit
#IBDesignable
class LineView: UIView {
#IBInspectable var lineWidth: CGFloat = 1.0
#IBInspectable var lineColor: UIColor? {
didSet {
lineCGColor = lineColor?.cgColor
}
}
var lineCGColor: CGColor?
override func draw(_ rect: CGRect) {
// Draw a line from the left to the right at the midpoint of the view's rect height.
let midpoint = self.bounds.size.height / 2.0
if let context = UIGraphicsGetCurrentContext() {
context.setLineWidth(lineWidth)
if let lineCGColor = self.lineCGColor {
context.setStrokeColor(lineCGColor)
}
else {
context.setStrokeColor(UIColor.black.cgColor)
}
context.move(to: CGPoint(x: 0.0, y: midpoint))
context.addLine(to: CGPoint(x: self.bounds.size.width, y: midpoint))
context.strokePath()
}
}
}

New code
This should work for horizontal and vertical lines:
/// <summary>
/// Used as separator line between UIView elements with a given color.
/// </summary>
public class DividerView : UIView
{
private UIColor color;
public DividerView ()
{
this.color = UIColor.Black;
}
public DividerView (UIColor color)
{
this.color = color;
}
public override void Draw (CGRect rect)
{
base.Draw (rect);
// get graphics context
CGContext context = UIGraphics.GetCurrentContext ();
// set up drawing attributes
color.SetStroke ();
color.SetFill ();
// assumption: we can determine if the line is horizontal/vertical based on it's size
nfloat lineWidth = 0;
nfloat xStartPosition = 0;
nfloat yStartPosition = 0;
nfloat xEndPosition = 0;
nfloat yEndPosition = 0;
if (rect.Width > rect.Height) {
// horizontal line
lineWidth = rect.Height;
xStartPosition = rect.X;
// Move the path down by half of the line width so it doesn't straddle pixels.
yStartPosition = rect.Y + lineWidth * 0.5f;
xEndPosition = rect.X + rect.Width;
yEndPosition = yStartPosition;
} else {
// vertical line
lineWidth = rect.Width;
// Move the path down by half of the line width so it doesn't straddle pixels.
xStartPosition = rect.X + lineWidth * 0.5f;
yStartPosition = rect.Y;
xEndPosition = xStartPosition;
yEndPosition = rect.Y + rect.Height;
}
// start point
context.MoveTo (xStartPosition, yStartPosition);
// end point
context.AddLineToPoint (xEndPosition, yEndPosition);
context.SetLineWidth (lineWidth);
// draw the path
context.DrawPath (CGPathDrawingMode.Stroke);
}
}
Original answer
Using frames in an Auto Layout project seems not suitable for me. Also I'd need the actual frames after auto layout has been applied and than I'd have to draw another view on top of it. In a fixed layout based on frames it is no problem, but not here. That's why I chose the following apporach for now:
I created a subclass of UIView and overwrote drawRect like in Draw line in UIView or how do you draw a line programmatically from a view controller?. Here is another option.
Because I'm using C# I give the code samples in that programming language. In the links I posted you can get the Objective-C version if you want.
DividerView:
using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.CoreGraphics;
using System.CodeDom.Compiler;
using System.Drawing;
namespace ContainerProject
{
public class DividerView : UIView
{
public DividerView ()
{
}
public override void Draw (RectangleF rect)
{
base.Draw (rect);
// get graphics context
CGContext context = UIGraphics.GetCurrentContext ();
// set up drawing attributes
UIColor.Black.SetStroke ();
UIColor.Black.SetFill ();
context.SetLineWidth (rect.Width);
// start point
context.MoveTo (rect.X, 0.0f);
// end point
context.AddLineToPoint (rect.X, rect.Y + rect.Height);
// draw the path
context.DrawPath (CGPathDrawingMode.Stroke);
}
}
}
In viewDidLoad of my container class I instantiate the DividerView with DividerView separator = new DividerView ();.
Auto Layout:
Then I'm using Auto Layout to layout it's position (all in viewDidLoad):
separator.TranslatesAutoresizingMaskIntoConstraints = false;
separatorTop = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Top, NSLayoutRelation.Equal, TopLayoutGuide, NSLayoutAttribute.Bottom, 1, 0);
separatorBottom = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, BottomLayoutGuide, NSLayoutAttribute.Top, 1, 0);
separatorRight = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Right, NSLayoutRelation.Equal, documentListController.View, NSLayoutAttribute.Left, 1, 0);
separatorWidth = NSLayoutConstraint.Create (separator, NSLayoutAttribute.Width, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1, 1);
this.View.AddSubview (separator);
Here the constraints are added:
public override void UpdateViewConstraints ()
{
if (!hasLoadedConstraints) {
this.View.AddConstraints (new NSLayoutConstraint[] {
separatorTop,
separatorBottom,
separatorRight,
separatorWidth
});
hasLoadedConstraints = true;
}
base.UpdateViewConstraints ();
}
Result:
This approach seems to work. Now my separator line is overlapping my detail view (only the first point). One could adapt the values or changes the constraints of the master and the detail view so that there is room between those for the separator.
Alternatives:
One could also add a subview to the content view of each UITableViewCell. Examples can be found here or here.

MHarper's code updated for Swift 5:
import UIKit
#IBDesignable
class LineView: UIView {
#IBInspectable var lineWidth: CGFloat = 1.0
#IBInspectable var lineColor: UIColor? {
didSet {
lineCGColor = lineColor?.cgColor
}
}
var lineCGColor: CGColor?
override func draw(_ rect: CGRect) {
// Draw a line from the left to the right at the midpoint of the view's rect height.
let midpoint = self.bounds.size.height / 2.0
let context = UIGraphicsGetCurrentContext()
context!.setLineWidth(lineWidth)
if let lineCGColor = self.lineCGColor {
context!.setStrokeColor(lineCGColor)
}
else {
context!.setStrokeColor(UIColor.black.cgColor)
}
context!.move(to: CGPoint(x: 0.0, y: midpoint))
context!.addLine(to: CGPoint(x: self.bounds.size.width, y: midpoint))
context!.strokePath()
}
}

Related

How can I get the coordinates of a Label inside a view?

What I am trying to do is to get the position of my label (timerLabel) in order to pass those coordinates to UIBezierPath (so that the center of the shape and the center of the label coincide).
Here's my code so far, inside the viewDidLoad method, using Xcode 13.2.1:
// getting the center of the label
let center = CGPoint.init(x: timerLabel.frame.midX , y: timerLabel.frame.midY)
// drawing the shape
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
and this is what I have when I run my app:
link
What I don't understand is why I get (0,0) as coordinates even though I access the label's property (timerLabel.frame.midX).
The coordinates of your label may vary depending on current layout. You need to track all changes and reposition your circle when changes occur. In view controller that uses constraints you would override
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// recreate your circle here
}
this alone does not explain why your circle is so far out. First of all, looking at your image you do not get (0, 0) but some other value which may be relative position of your label within the blue bubble. The frame is always relative to its superview so you need to convert that into your own coordinate system:
let targetView = self.view!
let sourceView = timerLabel!
let centerOfSourceViewInTargetView: CGPoint = targetView.convert(CGPoint(x: sourceView.bounds.midX, y: sourceView.bounds.midY), to: targetView)
// Use centerOfSourceViewInTargetView as center
but I suggest using neither of the two. If you are using constraints (which you should) then rather create more views than adding layers to your existing views.
For instance you could try something like this:
#IBDesignable class CircleView: UIView {
#IBInspectable var lineWidth: CGFloat = 10 { didSet { refresh() } }
#IBInspectable var strokeColor: UIColor = .lightGray { didSet { refresh() } }
override var frame: CGRect { didSet { refresh() } }
override func layoutSubviews() {
super.layoutSubviews()
refresh()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let fillRadius: CGFloat = min(bounds.width, bounds.height)*0.5
let strokeRadius: CGFloat = fillRadius - lineWidth*0.5
let path = UIBezierPath(ovalIn: .init(x: bounds.midX-strokeRadius, y: bounds.midY-strokeRadius, width: strokeRadius*2.0, height: strokeRadius*2.0))
path.lineWidth = lineWidth
strokeColor.setStroke()
UIColor.clear.setFill() // Probably not needed
path.stroke()
}
private func refresh() {
setNeedsDisplay() // This is to force redraw
}
}
this view should draw your circle within itself by overriding draw rect method. You can easily use it in your storyboard (first time it might not draw in storyboard because Xcode. Simply close your project and reopen it and you should see the circle even in storyboard).
Also in storyboard you can directly modify both line width and stroke color which is very convenient.
About the code:
Using #IBDesignable to see drawing in storyboard
Using #IBInspectable to be able to set values in storyboard
Refreshing on any value change to force redraw (sometimes needed)
When frame changes forcing a redraw (Needed when setting frame from code)
A method layoutSubviews is called when resized from constraints. Again redrawing.
Path is computed so that it fits within the size of view.

Building a circular facepile of profile pictures in Swift: how to have the last photo tucked under the first?

I am trying to build a UIView that has a few UIImageViews arranged in a circular, overlapping manner (see image below). Let's say we have N images. Drawing out the first N - 1 is easy, just use sin/cos functions to arrange the centers of the UIImageViews around a circle. The problem is with the last image that seemingly has two z-index values! I know this is possible since kik messenger has similar group profile photos.
The best idea I have come up so far is taking the last image, split into something like "top half" and "bottom half" and assign different z-values for each. This seems doable when the image is the left-most one, but what happens if the image is the top most? In this case, I would need to split left and right instead of top and bottom.
Because of this problem, it's probably not top, left, or right, but more like a split across some imaginary axis from the center of the overall facepile through the center of the UIImageView. How would I do that?!
Below Code Will Layout UIImageView's in Circle
You would need to import SDWebImage and provide some image URLs to run the code below.
import Foundation
import UIKit
import SDWebImage
class EventDetailsFacepileView: UIView {
static let dimension: CGFloat = 66.0
static let radius: CGFloat = dimension / 1.68
private var profilePicViews: [UIImageView] = []
var profilePicURLs: [URL] = [] {
didSet {
updateView()
}
}
func updateView() {
self.profilePicViews = profilePicURLs.map({ (profilePic) -> UIImageView in
let imageView = UIImageView()
imageView.sd_setImage(with: profilePic)
imageView.roundImage(imageDimension: EventDetailsFacepileView.dimension, showsBorder: true)
imageView.sd_imageTransition = .fade
return imageView
})
self.profilePicViews.forEach { (imageView) in
self.addSubview(imageView)
}
self.setNeedsLayout()
self.layer.borderColor = UIColor.green.cgColor
self.layer.borderWidth = 2
}
override func layoutSubviews() {
super.layoutSubviews()
let xOffset: CGFloat = 0
let yOffset: CGFloat = 0
let center = CGPoint(x: self.bounds.size.width / 2, y: self.bounds.size.height / 2)
let radius: CGFloat = EventDetailsFacepileView.radius
let angleStep: CGFloat = 2 * CGFloat(Double.pi) / CGFloat(profilePicViews.count)
var count = 0
for profilePicView in profilePicViews {
let xPos = center.x + CGFloat(cosf(Float(angleStep) * Float(count))) * (radius - xOffset)
let yPos = center.y + CGFloat(sinf(Float(angleStep) * Float(count))) * (radius - yOffset)
profilePicView.frame = CGRect(origin: CGPoint(x: xPos, y: yPos),
size: CGSize(width: EventDetailsFacepileView.dimension, height: EventDetailsFacepileView.dimension))
count += 1
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let requiredSize = EventDetailsFacepileView.dimension + EventDetailsFacepileView.radius
return CGSize(width: requiredSize,
height: requiredSize)
}
}
I don't think you'll have much success trying to split images to get over/under z-indexes.
One approach is to use masks to make it appear that the image views are overlapped.
The general idea would be:
subclass UIImageView
in layoutSubviews()
apply cornerRadius to layer to make the image round
get a rect from the "overlapping view"
convert that rect to local coordinates
expand that rect by the desired width of the "outline"
get an oval path from that rect
combine it with a path from self
apply it as a mask layer
Here is an example....
I was not entirely sure what your sizing calculations were doing... trying to use your EventDetailsFacepileView as-is gave me small images in the lower-right corner of the view?
So, I modified your EventDetailsFacepileView in a couple ways:
uses local images named "pro1" through "pro5" (you should be able to replace with your SDWebImage)
uses auto-layout constraints instead of explicit frames
uses MyOverlapImageView class to handle the masking
Code - no #IBOutlet connections, so just set a blank view controller to OverlapTestViewController:
class OverlapTestViewController: UIViewController {
let facePileView = MyFacePileView()
override func viewDidLoad() {
super.viewDidLoad()
facePileView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(facePileView)
facePileView.dimension = 120
let sz = facePileView.sizeThatFits(.zero)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
facePileView.widthAnchor.constraint(equalToConstant: sz.width),
facePileView.heightAnchor.constraint(equalTo: facePileView.widthAnchor),
facePileView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
facePileView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
facePileView.profilePicNames = [
"pro1", "pro2", "pro3", "pro4", "pro5"
]
}
}
class MyFacePileView: UIView {
var dimension: CGFloat = 66.0
lazy var radius: CGFloat = dimension / 1.68
private var profilePicViews: [MyOverlapImageView] = []
var profilePicNames: [String] = [] {
didSet {
updateView()
}
}
func updateView() {
self.profilePicViews = profilePicNames.map({ (profilePic) -> MyOverlapImageView in
let imageView = MyOverlapImageView()
if let img = UIImage(named: profilePic) {
imageView.image = img
}
return imageView
})
// add MyOverlapImageViews to self
// and set width / height constraints
self.profilePicViews.forEach { (imageView) in
self.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: dimension).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
}
// start at "12 o'clock"
var curAngle: CGFloat = .pi * 1.5
// angle increment
let incAngle: CGFloat = ( 360.0 / CGFloat(self.profilePicViews.count) ) * .pi / 180.0
// calculate position for each image view
// set center constraints
self.profilePicViews.forEach { imgView in
let xPos = cos(curAngle) * radius
let yPos = sin(curAngle) * radius
imgView.centerXAnchor.constraint(equalTo: centerXAnchor, constant: xPos).isActive = true
imgView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: yPos).isActive = true
curAngle += incAngle
}
// set "overlapView" property for each image view
let n = self.profilePicViews.count
for i in (1..<n).reversed() {
self.profilePicViews[i].overlapView = self.profilePicViews[i-1]
}
self.profilePicViews[0].overlapView = self.profilePicViews[n - 1]
self.layer.borderColor = UIColor.green.cgColor
self.layer.borderWidth = 2
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let requiredSize = dimension * 2.0 + radius / 2.0
return CGSize(width: requiredSize,
height: requiredSize)
}
}
class MyOverlapImageView: UIImageView {
// reference to the view that is overlapping me
weak var overlapView: MyOverlapImageView?
// width of "outline"
var outlineWidth: CGFloat = 6
override func layoutSubviews() {
super.layoutSubviews()
// make image round
layer.cornerRadius = bounds.size.width * 0.5
layer.masksToBounds = true
let mask = CAShapeLayer()
if let v = overlapView {
// get bounds from overlapView
// converted to self
// inset by outlineWidth (negative numbers will make it grow)
let maskRect = v.convert(v.bounds, to: self).insetBy(dx: -outlineWidth, dy: -outlineWidth)
// oval path from mask rect
let path = UIBezierPath(ovalIn: maskRect)
// path from self bounds
let clipPath = UIBezierPath(rect: bounds)
// append paths
clipPath.append(path)
mask.path = clipPath.cgPath
mask.fillRule = .evenOdd
// apply mask
layer.mask = mask
}
}
}
Result:
(I grabbed random images by searching google for sample profile pictures)

How to pass arguments from one class into a UIView class? Swift

I have a UIView class in my app which plots a line graph. In there, I assign my graphPoints variables like so :
var graphPoints:[Int] = [1,2,3,5,7,9]
var graphPoints2:[Int] = [1,2,3,5,7,9]
What I want to do is pass an array of Int from another class and assign those variables, but I am not sure how to do it. Initially i put all my code into one func with array [Int] as parameters and called it from another class but it stopped plotting the graph altogether. How do i do this?
Here is my UIVIew GraphPlotter class code :
import UIKit
#IBDesignable class GraphPlotter: UIView {
var graphPoints:[Int] = [1,2,3,5,7,9]
var graphPoints2:[Int] = [1,2,3,5,7,9]
//1 - the properties for the gradient
var startColor: UIColor = UIColor.redColor()
var endColor: UIColor = UIColor.greenColor()
override func drawRect(rect: CGRect) {
let width = rect.width
let height = rect.height
//set up background clipping area
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: UIRectCorner.AllCorners,
cornerRadii: CGSize(width: 8.0, height: 8.0))
path.addClip()
//2 - get the current context
let context = UIGraphicsGetCurrentContext()
let colors = [startColor.CGColor, endColor.CGColor]
//3 - set up the color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
//4 - set up the color stops
let colorLocations:[CGFloat] = [0.0, 1.0]
//5 - create the gradient
let gradient = CGGradientCreateWithColors(colorSpace,
colors,
colorLocations)
//6 - draw the gradient
var startPoint = CGPoint.zero
var endPoint = CGPoint(x:0, y:self.bounds.height)
CGContextDrawLinearGradient(context,
gradient,
startPoint,
endPoint,
[])
//calculate the x point
let margin:CGFloat = 40.0
let columnXPoint = { (column:Int) -> CGFloat in
//Calculate gap between points
let spacer = (width - margin*2 - 4) /
CGFloat((self.graphPoints.count - 1))
var x:CGFloat = CGFloat(column) * spacer
x += margin + 2
return x
}
// calculate the y point
let topBorder:CGFloat = 60
let bottomBorder:CGFloat = 50
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints2.maxElement()!
let columnYPoint = { (graphPoint2:Int) -> CGFloat in
var y:CGFloat = CGFloat(graphPoint2) /
CGFloat(maxValue) * graphHeight
y = graphHeight + topBorder - y // Flip the graph
return y
}
// draw the line graph
UIColor.flatTealColor().setFill()
UIColor.flatTealColor().setStroke()
//set up the points line
let graphPath = UIBezierPath()
//go to start of line
graphPath.moveToPoint(CGPoint(x:columnXPoint(0),
y:columnYPoint(graphPoints2[0])))
//add points for each item in the graphPoints array
//at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x:columnXPoint(i),
y:columnYPoint(graphPoints2[i]))
graphPath.addLineToPoint(nextPoint)
}
//Create the clipping path for the graph gradient
//1 - save the state of the context (commented out for now)
CGContextSaveGState(context)
//2 - make a copy of the path
let clippingPath = graphPath.copy() as! UIBezierPath
//3 - add lines to the copied path to complete the clip area
clippingPath.addLineToPoint(CGPoint(
x: columnXPoint(graphPoints.count - 1),
y:height))
clippingPath.addLineToPoint(CGPoint(
x:columnXPoint(0),
y:height))
clippingPath.closePath()
//4 - add the clipping path to the context
clippingPath.addClip()
let highestYPoint = columnYPoint(maxValue)
startPoint = CGPoint(x:margin, y: highestYPoint)
endPoint = CGPoint(x:margin, y:self.bounds.height)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, [])
CGContextRestoreGState(context)
//draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()
//Draw the circles on top of graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints2[i]))
point.x -= 5.0/2
point.y -= 5.0/2
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
circle.fill()
let label = UILabel(frame: CGRectMake(0, 0, 200, 21))
label.center = CGPointMake(160, 284)
label.textAlignment = NSTextAlignment.Center
// label.text = "I'am a test label"
self.addSubview(label)
}
//Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
//top line
linePath.moveToPoint(CGPoint(x:margin, y: topBorder))
linePath.addLineToPoint(CGPoint(x: width - margin,
y:topBorder))
//center line
linePath.moveToPoint(CGPoint(x:margin,
y: graphHeight/2 + topBorder))
linePath.addLineToPoint(CGPoint(x:width - margin,
y:graphHeight/2 + topBorder))
//bottom line
linePath.moveToPoint(CGPoint(x:margin,
y:height - bottomBorder))
linePath.addLineToPoint(CGPoint(x:width - margin,
y:height - bottomBorder))
let color = UIColor.flatTealColor()
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
}
}
DBController, func dosmth where I pass the array :
func dosmth(metadata: DBMetadata!) {
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let localFilePath = (documentsDirectoryPath as NSString).stringByAppendingPathComponent(metadata.filename)
var newarray = [Int]()
do{
let data = try String(contentsOfFile: localFilePath as String,
encoding: NSASCIIStringEncoding)
print(data)
newarray = data.characters.split(){$0 == ","}.map{
Int(String.init($0).stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()))!}
print(newarray)
}
catch let error { print(error) }
//Probably wrong
GraphPlotter().graphPoints = newarray
GraphPlotter().graphPoints2 = newarray
}
So your drawRect method is based on the two variables graphPoints and graphPoints2. Create a method whose job is to update the arrays of these two variables, and then invoke setNeedsDisplay - which will go on to redraw the view.
func plotGraphPoints(gpArray1 : [Int], andMorePoints gpArray2: [Int] ) {
print("Old Values", self.graphPoints)
self.graphPoints = gpArray1
self.graphPoints2 = gpArray2
print("New values", self.graphPoints)
self.setNeedsDisplay()
}
First, I'd set these up so that any update will redraw the view:
var graphPoints:[Int]? { didSet { setNeedsDisplay() } }
var graphPoints2:[Int]? { didSet { setNeedsDisplay() } }
Note, I made those optionals, because you generally want it to handle the absence of data with nil values rather than dummy values. This does assume, though, that you'll tweak your implementation to detect and handle these nil values, e.g., before you start drawing the lines, do a
guard graphPoints != nil && graphPoints2 != nil else { return }
But, I notice that this whole class is IBDesignable, in which case, you probably want a prepareForInterfaceBuilder that provides sample data:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
graphPoints = [1,2,3,5,7,9]
graphPoints2 = [1,2,3,5,7,9]
}
Second, your other class needs to have a reference to this custom view.
If this "other" class is the view controller and you added the custom view via IB, you would just add a #IBOutlet for the custom view to this view controller. If you added this custom view programmatically, you'd just keep a reference to it in some property after adding it to the view hierarchy. But, however you added a reference to that view, say graphView, you'd just set these properties:
graphView.graphPoints = ...
graphView.graphPoints2 = ...
If this "other" class is something other than a view controller (and in discussion, it sounds like the class in question is a controller for processing of asynchronous DropBox API), you also need to give that class some mechanism to reference the view controller (and thus the custom view). You can accomplish this by either implementing a "completion handler pattern" or a "delegate-protocol" pattern.

Set bool for UIButton Class in View controller (Swift)

I have a Custom Class for my UIButton. In this class I have the code below to draw the button. I would like the user to tap a button and toggle one part hidden/ not hidden. I don't know how to set the Bool value for this. Thanks
import UIKit
class CheckboxChecked: UIButton {
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
func drawTicked(#showTick: Bool) {
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(3, 3, 34, 34), cornerRadius: 10)
UIColor.darkGrayColor().setStroke()
rectanglePath.lineWidth = 3
rectanglePath.stroke()
if (showTick) {
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(9.5, 19.5))
bezierPath.addLineToPoint(CGPointMake(18.5, 26.5))
bezierPath.addLineToPoint(CGPointMake(30.5, 10.5))
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.darkGrayColor().setStroke()
bezierPath.lineWidth = 3
bezierPath.stroke()
}
}
override func drawRect(rect: CGRect) {
// Drawing code
}
}
I would suggest that instead you make a new UIView with an internal UIButton component - as well as what ever other component you want to hide.
You can then add a click handler to your UIButton portion of the control which would then control the .visible state of the other component. In fact you could probably mock this up in a Playground.
And if you wanted to get really fancy you could look at #IBInspectible Check out this post on custom components: https://www.weheartswift.com/make-awesome-ui-components-ios-8-using-swift-xcode-6/
I managed to achieve this by using the code below.
I have a UIButton class with this:
override func drawRect(rect: CGRect) {
// Drawing code
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(3, 3, 34, 34), cornerRadius: 10)
UIColor.darkGrayColor().setStroke()
rectanglePath.lineWidth = 3
rectanglePath.stroke()
if (darwCheck == true) {
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(9.5, 19.5))
bezierPath.addLineToPoint(CGPointMake(18.5, 26.5))
bezierPath.addLineToPoint(CGPointMake(30.5, 10.5))
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.darkGrayColor().setStroke()
bezierPath.lineWidth = 3
bezierPath.stroke()
}
}
And in the ViewController I had a button that changed a global Boolean variable and redrew the button.
drawCheck = true
checkboxButton.setNeedsDisplay()

trouble creating a scrollable UIView (swift + Interface Builder)

Please do not immediately discredit this a duplicate, I have been on stackoverflow for like 2 days now trying any and every way to do this, read a lot of blog articles (which might have been slightly dated) and still no luck.
Basically, I have a custom UIView which is meant to draw a lot of circles (it's just the starting point) and I cannot get the view to scroll down to the ones apart from the visible circles on initial load. I made the for loop for about 200 of them to be sure there is something to scroll to.
here is my view code:
import UIKit
class Draw2D: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
let colorSpace = CGColorSpaceCreateDeviceRGB();
let components: [CGFloat] = [0.0, 0.0, 1.0, 1.0];
let color = CGColorCreate(colorSpace, components);
CGContextSetStrokeColorWithColor(context, color);
var ctr = 0;
var ctr2 = 0;
for(var i:Int = 1; i<200; i++){
var ii: CGFloat=CGFloat(10+ctr);
var iii: CGFloat = CGFloat(30+ctr2);
CGContextStrokeEllipseInRect(context, CGRectMake(ii, iii, 33, 33));
if(ctr<200)
{
ctr+=160;
}else{
ctr=0;
ctr2+=50;
}
}
CGContextStrokePath(context);
}
}
In interface builder, I set the view class to Draw2D and the controller to my custom ViewController class. The view controller class is currently a standard one with no additional options added.
I tried dragging a Scroll View on the screen, I tried deleting the standard view, adding the scroll view and then putting my Draw2D view on top of that and it didn't work. I tried changing the size of the scroll view to make it smaller than my content (draw2d) view and nothing. I tried unchecking auto-layout and also changing simulated size to freeform as per certain tutorials I found online.
I also at one point tried creating a scroll view programmatically in the UIView and adding it as a subview which didn't help, maybe I did it wrong, I don't know but I'm really running out of options and sources to generate ideas from.
First, you can inherit from UIScrollView directly:
class Draw2D: UIScrollView {
Next, you need to set the content size of the UIScrollView to the height of the elements you inserted. For instance, let's say you have 4 circles:
let radius: CGFloat = 200
var contentHeight: CGFloat = 0
for i in 0...4 {
// initialize circle here
var circle = UIView()
// customize the view
circle.layer.cornerRadius = radius/2
circle.backgroundColor = UIColor.redColor()
// set the frame of the circle
circle.frame = CGRect(x: 0, y: CGFloat(i)*radius, width: radius, height: radius)
// add to scroll view
self.addSubview(circle)
// keep track of the height of the content
contentHeight += radius
}
At this point you have 4 circles one after the other, summing up to 800 px in height. Thus, you would set the content size of the scroll view as follows:
self.contentSize = CGSize(width: self.frame.width, height: contentHeight)
As far as contentHeight > device screen height then your view will scroll.
Full working code below:
import UIKit
import Foundation
class Draw2D: UIScrollView {
override init(frame: CGRect) {
super.init(frame: frame)
let radius: CGFloat = 200
var contentHeight: CGFloat = 0
for i in 0...4 {
// initialize circle here
var circle = UIView()
// customize the view
circle.layer.cornerRadius = radius/2
circle.backgroundColor = UIColor.redColor()
// set the frame of the circle
circle.frame = CGRect(x: 0, y: CGFloat(i)*radius, width: radius, height: radius)
// add to scroll view
self.addSubview(circle)
// keep track of the height of the content
contentHeight += radius
}
self.contentSize = CGSize(width: self.frame.width, height: contentHeight)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources