Drawing underline for UIButton is hidden behind views - ios

I have a viewcontroller with 3 UIButtons that have the same Y position but 20px apart from each other horizontally. When a button is selected, I'd like to draw a line beneath the button. I tried just adding the underline attribute, but the line isn't really customizable. I have the buttons on top of a UIImageView that is contained within a subclass of UIView I named SectionView.
In my view controller i have the following function that is called when a button is pressed:
#IBOutlet weak var sectionView: SectionView!
func sectionButtonPressed(sender: UIButton) {
self.sectionView.selectedButton = sender
self.sectionView.setNeedsDisplay()
}
In my UIView subclass, I have the following:
class SectionView: UIView {
var selectedButton: UIButton?
override func drawRect(rect: CGRect) {
let linePath = UIBezierPath()
linePath.moveToPoint(CGPoint(x:(self.selectedButton?.frame.origin.x)! - 3, y: (self.selectedButton?.frame.origin.y)! + 35))
linePath.addLineToPoint(CGPoint(x: (self.selectedButton?.frame.origin.x)! + (self.selectedButton?.frame.width)! + 3, y: (self.selectedButton?.frame.origin.y)! + 35))
linePath.closePath()
UIColor.purpleColor().set()
linePath.stroke()
linePath.fill()
}
}
I can get the line to draw at the position and length I desire, but its beneath the UIButton and UIImageView. How can I get it to appear above?

func addLine(button:UIButton)// pass button object as a argument
{
let length = button.bounds.width
let x = button.bounds.origin.x
let y = button.bounds.origin.y + button.bounds.height - 5
let path = UIBezierPath()
path.move(to: CGPoint(x: x, y: y))
path.addLine(to: CGPoint(x: x + length , y:y))
//design path in layer
let lineLayer = CAShapeLayer()
lineLayer.path = path.cgPath
lineLayer.strokeColor = UIColor.red.cgColor
lineLayer.lineWidth = 1.0
lineLayer.fillColor = UIColor.clear.cgColor
button.layer.insertSublayer(lineLayer, at: 0)
}`
// This code will create one line below your button(using UIBezierPath and Layers )

I had to change it up. Instead of using a bezier path, I ended up creating a UIView and placing it above the other views.
func sectionButtonPressed(sender: UIButton) {
let lineView = createView(sender)
self.sectionView.addSubview(lineView)
}
func createView(sender:UIButton) -> UIView {
let customView = UIView(frame: CGRect(x: sender.frame.origin.x, y: sender.frame.origin.y + 12, width: sender.frame.width, height: 2.0))
customView.backgroundColor = UIColor.purpleColor()
return customView
}
Remember, my buttons are on top of a UIImageView that is contained within a UIView. When I tried to add the line view as a subview to the imageview, the line wouldn't line up with the x origin of the button. So instead I added the line view as a subview to the UIView and it came out looking correct.

We can use CALayer to draw lines. Add the following code in your button action, this may help.
#IBAction func sectionButtonPressed(sender: AnyObject) {
let border = CALayer()
border.borderColor = UIColor.purpleColor().CGColor
border.frame = CGRect(x: 0, y: sender.frame.size.height - 2,
width: sender.frame.size.width, height: 2)
border.borderWidth = 2.0
sender.layer.addSublayer(border)
sender.layer.masksToBounds = true
}
RESULT:

Related

Swift - How to recognise gesture interaction of child view which part is located outside to of the parent view

I created anchor points for different shapes which can help resize the shape. This works fine but the issue is with the anchor point gesture recognizer area (light blue part). You can move the anchor point when it's pressed within the shape but the red areas cannot be selected to move the anchor point as it's located outside the parent view.
The question I have is, is there a way to recognize the red area as it is a child of the shape but the red area is located outside the parent view?
I thought maybe I would extend the frame size of the shape to include the anchor points and recalculate the shape area removing the width of the anchor points but not sure if this is the best way.
below is the anchor point view attached to the shape view
class AnchorPoint: UIView {
var panGestureStartLocation: CGPoint?
var dot = UIView()
var positionName: String = ""
func config(positionName: String, position: CGPoint){
self.positionName = positionName
self.frame = CGRect(x: 0, y: 0, width: 60, height: 60)
self.layer.cornerRadius = self.frame.height / 2
self.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.5)
self.center = position
self.isUserInteractionEnabled = true
}
func addDot(){
dot = UIView()
dot.frame = CGRect(x: (self.bounds.size.width / 2) - 10, y: (self.bounds.size.height / 2) - 10, width: 20, height: 20)
dot.layer.cornerRadius = 20 / 2
dot.backgroundColor = UIColor.green.withAlphaComponent(0.6)
dot.layer.borderWidth = 2
dot.layer.borderColor = UIColor.blue.cgColor
dot.isUserInteractionEnabled = true
addSubview(dot)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var hitView = super.hitTest(point, with: event)
if hitView == nil {
let npoint = dot.convert(point, from: self)
hitView = dot.hitTest(npoint, with: event)
}
return nil
}

Swift : drawing the dots in loop

I am trying to draw multiple dots in a loop by passing set of points. But not a single dot is getting drawn on view. Idea - I am parsing xml document and extracting point to draw it on view.
Edit - I have updated the code with suggested changes and it is working.
//View Class
class DrawTrace: UIView
{
var points = [CGPoint]()
{
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect)
{
let size = CGSize(width: 1, height: 1)
UIColor.white.set()
for point in points
{
print(point.x,point.y)
let dot = UIBezierPath(ovalIn: CGRect(origin: point, size: size))
dot.fill()
}
}
}
//View Controller Class
class ViewController: UIViewController
{
var object : traceDoc = traceDoc()
let trace = DrawTrace(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
override func viewDidLoad()
{
super.viewDidLoad()
object.collectionOfData()
trace.points = object.tracePoints
self.view.addSubview(trace)
}
}
Added view instance to view hierarchy that is in view controller.Created instance of DrawTrace and appended to tracepoints array.
Your code is way more complex than it needs to be. One big issue is that you keep adding more and more layers each time draw is called.
There is no need to use layers to draw dots in your custom view. Here is a much simpler solution:
class DrawTrace: UIView
{
var points = [CGPoint]() {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect)
{
let size = CGSize(width: 2, height: 2)
UIColor.black.set()
for point in points {
let dot = UIBezierPath(ovalIn: CGRect(origin: point, size: size))
// this just draws the circle by filling it.
// Update any properties of the path as needed.
// Use dot.stroke() if you need an outline around the circle.
dot.fill()
}
}
}
let trace = DrawTrace(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
trace.backgroundColor = .white
trace.points = [ CGPoint(x: 10, y: 10), CGPoint(x: 35, y: 100)]
The above can be copied into a playground.
Black dotes on black view aren't visible, try to make dotes white:
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
Also, in UIViewController code you are not adding your DrawTrace to views hierarchy, try in viewDidLoad:
self.view.addSubview(drawTraceInstance)
Finally, it seems that you have to iterate over array of CGPoint's. Instead of use stride, user FastEnumeration over Array:
for point in points
{
drawDot(x: point.x, y: point.y,Color: fillColor, size: size)
}

Radial Gradience Colors Not Updating in UIView

I am attempting to use radial gradience within my app on a background UIView. My issue comes to play, where I want to update the view colors of the gradience multiple times. I have no errors with my code, but I can't seem to figure out how to get around this.
What I have tried is reloading the Input Views within the regular UIView as-well as the gradience class; remove the subview of the uiview, and adding a new view to the screen, which worked for only change of set colors; and I have looked over the internet, but can't seem to resolve this. All I want is for the UIView to update its colors based on the new color parameters I give it.
Here is my radial gradience code:
import UIKit
class RadialGradient: UIView {
var innerColor = UIColor.yellow
var outterColor = UIColor.red
override func draw(_ rect: CGRect) {
let colors = [innerColor.cgColor, outterColor.cgColor] as CFArray
let endRadius = min(frame.width, frame.height)
let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
UIGraphicsGetCurrentContext()!.drawRadialGradient(gradient!,
startCenter: center,
startRadius: 0.0,
endCenter: center,
endRadius: endRadius,
options: CGGradientDrawingOptions.drawsAfterEndLocation)
}
}
Here is where I am using it:
import UIKit
class TestIssuesVC: UIViewController {
var check : Bool = false
#IBAction func buttonClicked(_ sender: Any) {
if check == true {
backgroundsetting.removeFromSuperview()
print("Why wont you change to purple and black?????")
cheapFix(inner: UIColor.purple, outter: UIColor.black)
} else {
backgroundsetting.removeFromSuperview()
cheapFix(inner: UIColor.red, outter: UIColor.blue)
check = true
}
}
func cheapFix(inner: UIColor, outter: UIColor) {
let backgroundsetting = RadialGradient()
backgroundsetting.innerColor = inner
backgroundsetting.outterColor = outter
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
let backgroundsetting = RadialGradient()
override func viewWillAppear(_ animated: Bool) {
backgroundsetting.innerColor = UIColor.green
backgroundsetting.outterColor = UIColor.red
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
}
I see two things.
Your cheapFix method never updates the backgroundsetting property. It creates its own local variable of the same name. So you are actually adding new views over and over but each is sent to the back so you only ever see the first one. This is why nothing ever appears to change.
None of that is necessary. Simply create one RadialGradient view. When you want its colors to change, simply update its colors. That class needs to be fixed so it redraws itself when its properties are updated.
Make the following change to the two properties in your RadialGradient class:
var innerColor = UIColor.yellow {
didSet {
setNeedsDisplay()
}
}
var outterColor = UIColor.red {
didSet {
setNeedsDisplay()
}
}
Those changes will ensure the view redraws itself when its colors are updated.

Why is my table view shadow scrolling with my table view?

I added shadow to my table view but unfortunately when I scroll through the table view the shadow also moves with the table. The code for adding shadow is as follows:
func addShadow(to myView: UIView){
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: uiView.frame.width, height: uiView.frame.height * 1.1))
uiView.layer.shadowColor = UIColor.black.cgColor
uiView.layer.shadowOffset = CGSize(width: 0.2, height: 0)
uiView.layer.shadowOpacity = 0.5
uiView.layer.shadowRadius = 5.0
uiView.layer.masksToBounds = false
uiView.layer.shadowPath = shadowPath.cgPath
}
Can you please explain to me why is this happening and how to make the shadow stick to its designated location?
Thank you in advance.
If you are adding shadow on tableView, it will scroll along with tableview data. To prevent that you have to add UIView first. Add tableview on that view. Add shadow for the UIView you have taken. It will stick to designated location.
This is my solution in Swift 3 with an UIView and a CAGradientLayer inside.
override func viewWillAppear(_ animated: Bool) {
addShadow(myView: myTab)
}
func addShadow(myView: UIView){
let shadowView = UIView()
shadowView.center = CGPoint(x: myView.frame.minX,y:myView.frame.minY - 15)
shadowView.frame.size = CGSize(width: myView.frame.width, height: 15)
let gradient = CAGradientLayer()
gradient.frame.size = shadowView.frame.size
let stopColor = UIColor.clear.cgColor
let startColor = UIColor.black.withAlphaComponent(0.8).cgColor
gradient.colors = [stopColor,startColor]
gradient.locations = [0.0,1.0]
shadowView.layer.addSublayer(gradient)
view.addSubview(shadowView)
}

How to size SKView sub view and SKScene correctly

I am trying to get / set the correct size of a subview (SKView).
I am using the storyboard to create a UIView, and a subview that is a SKView. I would like to the programmatically create a SKScene with the dimensions of the SKView.
My thought is that scene.size.height and scene.size.width will be equal to the SKView's height and width. To test this, I am drawing four blue circles in each corner, and red lines on the borders. I am only able to see the bottom left corner, when I am expecting to see all four blue corner dots and boarder lines.
Please ignore the black circles in the Scene, they are irrelevant.
iPhone 6 Screenshot (portrait)
iPhone 6 Screenshot (landscape)
I added SW (South West), SE, NE, and NW labels
ViewController With SKView Reference
This is where I create the SKSCene (see func newGame)
import UIKit
import SpriteKit
class CenterView: UIViewController, ActionDelegate {
#IBOutlet weak private var navBar:UINavigationBar!
#IBOutlet weak private var titleBar:UINavigationItem!
#IBOutlet weak private var gameView:SKView!
var navigation:NavigationDelegate?
var action:ActionDelegate?
var game:GameDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.action = self
newGame()
}
#IBAction func menuClick(sender: AnyObject) {
navigation?.toggleLeftPanel()
}
func setTitleBarTitle(title: String) {
titleBar.title = title
}
func newGame() {
print("skview bounds: \(self.gameView.bounds.size)")
let game = GameScene(size: self.gameView.bounds.size)
self.game = game
game.action = action
game.scaleMode = .ResizeFill
self.gameView.presentScene(game)
}
}
Main.storyboard
Constraints
Adding Corner Circles & Border Lines
if let scene = self.scene {
let dot = SKShapeNode(circleOfRadius: 10)
dot.fillColor = UIColor.blueColor()
dot.position = CGPoint(x: 0,y: 0)
let dot1 = SKShapeNode(circleOfRadius: 10)
dot1.fillColor = UIColor.blueColor()
dot1.position = CGPoint(x: scene.size.width,y: 0)
let dot2 = SKShapeNode(circleOfRadius: 10)
dot2.fillColor = UIColor.blueColor()
dot2.position = CGPoint(x: 0,y: scene.size.height)
let dot3 = SKShapeNode(circleOfRadius: 10)
dot3.fillColor = UIColor.blueColor()
dot3.position = CGPoint(x: scene.size.width,y: scene.size.height)
let left = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 3, height: scene.size.height))
let top = SKShapeNode(rect: CGRect(x: 0, y: scene.size.height, width: scene.size.width, height: 3))
let right = SKShapeNode(rect: CGRect(x: scene.size.width, y: 0, width: 3, height: scene.size.height))
let bottom = SKShapeNode(rect: CGRect(x: 0, y: 0, width: scene.size.width, height: 3))
left.fillColor = UIColor.redColor()
top.fillColor = UIColor.redColor()
bottom.fillColor = UIColor.redColor()
right.fillColor = UIColor.redColor()
scene.addChild(dot)
scene.addChild(dot1)
scene.addChild(dot2)
scene.addChild(dot3)
scene.addChild(left)
scene.addChild(top)
scene.addChild(right)
scene.addChild(bottom)
}
It turned out that my constraints were not set up correctly.
Constraints
Result

Resources