Swift: UILabel has weird outlines? - ios

Ok, I have never experienced this but don't know how to make it go away: I have programmatically created several UILabels that have a certain background color. On some, however, on the upper and right borders this a weird darker line visible (see 2nd row of labels):
I don't know what is causing this.
Here is how I made them:
for i in 0...featureLabels.count-1
{
featureLabels[i] = RSSLinkLabel()
featureLabels[i]?.frame = CGRect(x: 0, y: 0, width: (overallWidth-(imgSpace))/3, height: ((overallWidth-(imgSpace))/3)/contentRatios[2])
featureLabels[i]?.text = "Hello label \(i)"
featureLabels[i]?.backgroundColor = UIColor(hexString: secondColorStr)
featureLabels[i]?.textAlignment = .left
featureLabels[i]?.font = iphoneFont
var pt = CGPoint()
switch i {
case 0:
pt = CGPoint(x: sideDiff + ((featureLabels[i]?.bounds.width)!/2), y: subContView2.bounds.height - ((featureLabels[i]?.bounds.height)!/2))
case 1:
pt = CGPoint(x: subContView2.bounds.width/2, y: subContView2.bounds.height - ((featureLabels[i]?.bounds.height)!/2))
case 2:
pt = CGPoint(x: sideDiff + (overallWidth - ((featureLabels[i]?.bounds.width)!/2)), y: subContView2.bounds.height - ((featureLabels[i]?.bounds.height)!/2))
default:
break
}
featureLabels[i]?.center = pt
subContView2.addSubview(featureLabels[i]!)
}
They are custom labels that I have subclassed here:
import UIKit
class RSSLinkLabel: UILabel {
var id = String()
override init (frame : CGRect) {
super.init(frame : frame)
}
func customInit()
{
self.text = txtSources[id]
}
required public init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
convenience init () {
self.init(frame:CGRect.zero)
}
override public func layoutSubviews() {
customInit()
}
}
I have tried doing featureLabels[i]?.layer.borderColor = UIColor.clear.cgColor but nothing happens.
How can I make that outline go away?

Related

Why is my SKShapeNode subclass not appearing when I run my app?

I am trying to have objects fall from the top of the screen so I created a FallingBlock subclass that inherits from SKShapeNode. However, whenever I run my code, the blocks do not appear on the screen. I know these blocks are on screen because they come in contact with other objects, but they are unable to be seen. Below is my code:
import SpriteKit
class FallingBlock: SKShapeNode {
let color = [UIColor.red,UIColor.cyan,UIColor.green,UIColor.yellow,UIColor.orange,UIColor.systemPink].randomElement() as! UIColor
init(side: CGPoint) {
super.init()
self.path = CGPath(roundedRect: CGRect(x: side.x, y: side.y, width: 20, height: 20), cornerWidth: 3, cornerHeight: 3, transform: nil)
self.fillColor = color
self.strokeColor = color
self.position = side
self.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 20))
self.physicsBody?.affectedByGravity = true
self.zPosition = 1
self.physicsBody?.categoryBitMask = PhysicsCategories.obstacleCategory
self.name = ObjectNames.obstacle
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("falling block reference deleted")
}
}
Within my GameScene I am running the following function:
func newObstacle() {
let side = [25,frame.width-25].randomElement() as! CGFloat
obstacle = FallingBlock(side: CGPoint(x: side, y: frame.maxY+15))
worldNode.addChild(obstacle)
deallocateObstacle()
}
I couldn't figure out a solution when the class inherits from an SKShapeNode; however, I was able to figure out how to do it when it inherits from an SKNode. All that is required is to initialize the SKNode and then add an SKShapeNode - with your desired attributes - as a child of the SKNode like what is done below:
class FallingBlock: SKNode {
init(side: CGPoint) {
super.init()
self.addNewBlock(side)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("falling block reference deleted")
}
private func addNewBlock(_ side: CGPoint) {
var color = [UIColor.red,UIColor.cyan,UIColor.green,UIColor.yellow,UIColor.orange,UIColor.systemPink].randomElement() as! UIColor
let obstacle = SKShapeNode(rectOf: CGSize(width: 20, height: 20), cornerRadius: 3)
obstacle.fillColor = color
obstacle.strokeColor = color
obstacle.position = side
obstacle.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 20))
obstacle.physicsBody?.affectedByGravity = true
obstacle.zPosition = 1
obstacle.physicsBody?.categoryBitMask = PhysicsCategories.obstacleCategory
obstacle.name = ObjectNames.obstacle
self.addChild(obstacle)
}
}

Creating a haptic-touch-like button in Swift/Obj-C

I've been playing around with an idea for a button which, when held, expands to reveal other buttons (like the FAB in Android). Without releasing, sliding one's finger down should highlight other buttons, similar to haptic touch's behaviour.
Here's a crude mockup I've created using Drama: https://youtu.be/Iam8Gjv3gqM.
There should also be an option to have a label horizontally next to each button, and the order of buttons should be as shown (with the selected button at the top instead of its usual position).
I already have a class for each button (seen below), but do not know how to achieve this layout/behaviour.
class iDUButton: UIButton {
init(image: UIImage? = nil) {
super.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
if let image = image {
setImage(image, for: .normal)
}
backgroundColor = .secondarySystemFill
layer.cornerRadius = 15
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
class iDUBadgeButton: iDUButton {
var badgeLabel = UILabel()
var badgeCount = 0
override init(image: UIImage? = nil) {
super.init(image: image)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
badgeLabel.textColor = .white
badgeLabel.backgroundColor = .systemRed
badgeLabel.textAlignment = .center
badgeLabel.font = .preferredFont(forTextStyle: .caption1)
badgeLabel.alpha = 0
updateBadge()
addSubview(badgeLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
badgeLabel.sizeToFit()
let height = max(18, badgeLabel.frame.height + 5.0)
let width = max(height, badgeLabel.frame.width + 10.0)
badgeLabel.frame = CGRect(x: frame.width - 5, y: -badgeLabel.frame.height / 2, width: width, height: height);
badgeLabel.layer.cornerRadius = badgeLabel.frame.height / 2;
badgeLabel.layer.masksToBounds = true;
}
func setBadgeCount(badgeCount: Int) {
self.badgeCount = badgeCount;
self.updateBadge()
}
func updateBadge() {
if badgeCount != 0 {
badgeLabel.text = "\(badgeCount)"
}
UIView.animate(withDuration: 0.25, animations: {
self.badgeLabel.alpha = self.badgeCount == 0 ? 0 : 1
})
layoutSubviews()
}
}
If anyone could help with achieving this layout, I'd be very grateful! Thanks in advance.
PS: I'm developing the UI in Swift, but will be converting it to Objective-C once complete. If you're interested, the use case is to adapt the button in the top-left of this to offer a selection of different tags.

Swift callback function for draw()?

I have a customView where I overwrite drawRect:. I also have some cases, where I want to display a UILabel ontop of my customView:
if self.ticks.count < 50 {
self.label.frame = self.bounds
self.label.text = "This chart requires 50 or more ticks.\nKeep Ticking."
self.addSubview(label)
}
However if I call this code before the drawRect is called, then my label doesn't show up. Is there a callback method like viewDidDraw or something?
EDIT
Someone asked for it, so here is all the code that is now doing what I need it to do. I think the answer to the question is to override layoutSubviews.
#IBDesignable open class CustomView: UIView {
#objc open var ticks:[Tick] = []
let label = UILabel()
required public init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
required public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup(){
self.backgroundColor = UIColor.clear
self.label.frame = self.bounds
self.label.text = "This chart requires 50 or more ticks.\nKeep Ticking."
self.label.numberOfLines = 0
self.label.textAlignment = .center
self.addSubview(label)
}
override open func layoutSubviews() {
self.label.frame = self.bounds
}
#objc open func setSessions(sessions:[Session]){
self.ticks = []
for session in sessions.reversed(){
if self.ticks.count < 250{
self.ticks = self.ticks + (session.getShots() as! [Shot])
}else{
break
}
}
if self.ticks.count < 50 {
self.label.isHidden = false
}else{
self.label.isHidden = true
}
self.bringSubview(toFront: self.label)
}
override open func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
let width = rect.width - 10;
let height = rect.height - 10;
let center = CGPoint(x: width/2+5, y: height/2+5)
let big_r = min(width, height)/2
context.setLineWidth(1.5)
UIColor.white.setStroke()
//draw the rings
for i in stride(from: 1, to: 7, by: 1){
let r = big_r * CGFloat(i) / 6.0;
context.addArc(center:center, radius:r, startAngle: 0, endAngle: CGFloat(2*Double.pi), clockwise: false)
context.strokePath()
}
//draw the ticks
if self.ticks.count > 49 {
UIColor.red().setFill()
for (i, tick) in self.ticks.prefix(250).enumerated(){
let radius = min((100.0-CGFloat(shot.score))/30.0 * big_r, big_r);
let x = Utilities.calculateX(tick, radius)
let y = Utilities.calculateY(tick, radius)
let point = CGPoint(x: x+center.x,y: y+center.y)
context.addArc(center:point, radius:CGFloat(DOT_WIDTH/2), startAngle: 0, endAngle: CGFloat(2*Double.pi), clockwise: false)
context.fillPath()
}
}
}
}
You can make your custom view redraw by calling setNeedsDisplay() when needed. After that, you can add the label
You can set a delegation for the View class and call the delegate method at the end of the draw function. However, it seems that if you call that directly, the draw function will only end until the delegate function get processed. Which means that the code will be executed before the new drawing refresh on the screen. In that case, you may need to call it asynchronously.
// at end of draw function
DispatchQueue.main.async {
self.delegate?.drawEnd()
}

How to make waved progress bar in swift?

I am beginner IOS developer and often see in design patterns that people use Heart Beat or Waves as the progression bar (i.e. song progress). Sometimes these progress bars go around theAlbum Art etc.
How can i achieve such thing? I am aware of UISlider combined with AVAudioPlayer but couldn't find anything to achieve such as following for song slider.
You could make a custom View, and draw the vertical lines manually. The main procedure for reference:
import UIKit
class WavedProgressView: UIView {
var lineMargin:CGFloat = 2.0
var volumes:[CGFloat] = [0.5,0.3,0.2,0.6,0.4,0.5,0.8,0.6,0.4]
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.darkGray
}
override var frame: CGRect {
didSet{
self.drawVerticalLines()
}
}
var lineWidth:CGFloat = 3.0{
didSet{
self.drawVerticalLines()
}
}
func drawVerticalLines() {
let linePath = CGMutablePath()
for i in 0..<self.volumes.count {
let height = self.frame.height * volumes[i]
let y = (self.frame.height - height) / 2.0
linePath.addRect(CGRect(x: lineMargin + (lineMargin + lineWidth) * CGFloat(i), y: y, width: lineWidth, height: height))
}
let lineLayer = CAShapeLayer()
lineLayer.path = linePath
lineLayer.lineWidth = 0.5
lineLayer.strokeColor = UIColor.white.cgColor
lineLayer.fillColor = UIColor.white.cgColor
self.layer.sublayers?.removeAll()
self.layer.addSublayer(lineLayer)
}
}
The effect is:
And please make it more perfect by yourself, like: handling event, applying default and highlighted color etc.

Setting the frame of a sublayer inside of layoutSubviews() makes changing it impossible. Any workaround for that?

I'm working on a customizable UITextField (see code below). I have added a border at the bottom (you can set it in the storyboard). However, I had problems setting the frame of the CALayer that this border consists of.
If I set it inside the didSet method of var showBottomBorder it doesn't appear on the screen. I think this is because the frame (of the UITextField) hasn't been calculated yet (maybe didSet gets called before that).
So I moved it to the layoutSubviews() method (see code below). This works perfectly.
But now I have another problem. I can't really change that frame anymore. Every time I change it, it gets reset by layoutSubviews() which I think is called then.
At the bottom of my code, there is the method textFieldDidBeginEditing. In there, I wanted to move up my bottom border (animated). But it doesn't work. The border does not move anywhere. And like I said, I think it's because I set the frame inside the layoutSubviews() method.
Is there a better way to set the frame of the bottom border? A way which allows me to change stuff?
#IBDesignable
class CustomizableTextField: UITextField, UITextFieldDelegate {
// MARK: - Properties
private var bottomBorder = CALayer()
// MARK: - #IBInspectables
#IBInspectable var roundCorners: CGFloat = 0 {
didSet {
self.layer.cornerRadius = roundCorners
self.clipsToBounds = true
}
}
/** -- */
#IBInspectable var borderWidth: CGFloat = 1.0 {
didSet {
self.layer.borderWidth = self.borderWidth
}
}
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
self.layer.borderColor = self.borderColor.cgColor
}
}
/** -- */
/** -- */
private var showBottomBorder: Bool = false {
didSet {
switch showBottomBorder {
case true:
bottomBorder.borderColor = self.bottomBorderColor.cgColor
bottomBorder.borderWidth = self.bottomBorderWidth
self.layer.addSublayer(bottomBorder)
self.layer.masksToBounds = true
break
case false:
bottomBorder.removeFromSuperlayer()
break
}
}
}
#IBInspectable var bottomBorderWidth: CGFloat = 1.0 {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
#IBInspectable var bottomBorderColor: UIColor = UIColor.white {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
/** -- */
/** -- */
// Somwhow, the default panel for my font color doesn't change anything, so I created this
#IBInspectable var fixedFontColor: UIColor = UIColor.white {
didSet {
self.textColor = fixedFontColor
}
}
#IBInspectable var placeholderFontColor: UIColor = UIColor.white {
didSet {
var placeholderTxt = ""
if let txt = self.placeholder {
placeholderTxt = txt
}
self.attributedPlaceholder = NSAttributedString(string: placeholderTxt, attributes: [NSForegroundColorAttributeName: placeholderFontColor])
}
}
/** -- */
// MARK: - Overrides and Initializers
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
// HERE
bottomBorder.frame = CGRect(x: 0, y: self.frame.size.height - self.bottomBorderWidth, width: self.frame.size.width, height: self.frame.size.height)
}
// setting the textField delegate to self
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//self.borderStyle = .none
self.delegate = self
}
// MARK: - Events
func textFieldDidBeginEditing(_ textField: UITextField) {
}
You can use and extension on UITextFiled for setting the border.
And keep a reference to it with KVC.
By overriding LayoutSubview, every the layout will change, we'l check if the border exists, if so remove it, and re-create a new one with the new frame:
import UIKit
let MyTopBorder = "myTopBorder"
let MyBottomBorder = "myBottomBorder"
struct Defaults {
static let width = CGFloat(1.0)
static func bottonBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: view.frame.size.height - Defaults.width, width: view.frame.size.width, height: view.frame.size.height)
}
static func topBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: CGFloat(0) , width: view.frame.size.width, height: Defaults.width)
}
}
extension UITextField
{
func setBottomBorder(color:CGColor)
{
if let isBottomBorder = self.getBottomBorderIfExists() {
isBottomBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.bottonBorderFrame(self), color: color, andKey: MyBottomBorder)
}
func setTopBorder(color:CGColor)
{
if let isTopBorder = self.getTopBorderIfExists() {
isTopBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.topBorderFrame(self), color: color, andKey: MyTopBorder)
}
func setBorderWithFrame(frame: CGRect, color: CGColor, andKey: String) {
self.borderStyle = UITextBorderStyle.None;
let border = CALayer()
border.borderColor = color
border.frame = frame
border.borderWidth = Defaults.width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
self.layer.setValue(border, forKey: andKey)
}
func removeTopBorder() {
if let isTopBorder = self.getTopBorderIfExists() {
self.layer.setValue(nil, forKey: MyTopBorder)
isTopBorder.removeFromSuperlayer()
}
}
func removeBottomBorder() {
if let isBottomBorder = self.getBottomBorderIfExists() {
self.layer.setValue(nil, forKey: MyBottomBorder)
isBottomBorder.removeFromSuperlayer()
}
}
private func getBorderIfExistsByKey(key: String)->CALayer? {
if let isBorderSet = self.layer.valueForKey(key) {
if let borderIsCALayer = isBorderSet as? CALayer {
return borderIsCALayer
}
}
return nil
}
private func getTopBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyTopBorder)
}
private func getBottomBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyBottomBorder)
}
public override func layoutSubviews() {
super.layoutSubviews()
// Update bottom on frame change
if let isBottomBorder = self.getBottomBorderIfExists() {
let borderColor = isBottomBorder .borderColor
self.removeBottomBorder()
self.setBottomBorder(borderColor!)
}
// Update top on frame change
if let isTopBorder = self.getTopBorderIfExists() {
let borderColor = isTopBorder.borderColor
self.removeTopBorder()
self.setTopBorder(borderColor!)
}
}
}
Usage:
let textField = UITextField(frame: CGRect(x: 100,y: 100, width: 100, height: 100))
textField.backgroundColor = UIColor.blueColor() // Thie color is for visulizing better
self.view.addSubview(textField)
textField.setBottomBorder(UIColor.blackColor().CGColor) // Now you have a border
textField.frame = CGRect(x: 150, y: 200, width: 200, height: 200) // And the border updated to the new frame
// Now if you would like to change from bottom to top, simply do this:
textField.removeBottomBorder()
textField.setTopBorder(UIColor.blackColor().CGColor)

Resources