iOS8: Auto-layout and Gradient - swift

Setup:
I have a View Controller that consists of a View and a Container View.
The View (Orange) is pinned to top 0, left 0, and right 0.
The Container View (Gray) is pinned to bottom 0, left 0, and right 0.
The View's Bottom Space to: Container View = 0
The View's Proportional Height to Container View = 1
Desired Results:
I would like to add gradient to the background of the View (Orange)
Tried:
I'm using Auto-layout with class sizes to get different behavior on different screen.
Code:
class ViewController: UIViewController {
#IBOutlet weak var graphView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let backgroundColor = CAGradientLayer().graphViewBackgroundColor()
backgroundColor.frame = self.graphView.frame
self.graphView.layer.addSublayer(backgroundColor)
}
I have a category:
extension CAGradientLayer {
func graphViewBackgroundColor() -> CAGradientLayer {
let topColor = UIColor(red: (160/255.0), green: (160/255.0), blue: (160/255.0), alpha: 1)
let bottomColor = UIColor(red: (52/255.0), green: (53/255.0), blue: (52/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
}
}
Result:
As you can see gradient did not cover the entire View.
Question: How can I get the gradient to cover the entire View
Update:
When I place the code in viewDidLayoutSubviews() It looks weird when I rotate:

Simply do it this inside viewDidLayoutSubviews:
override func viewDidLayoutSubview() {
super.viewDidLayoutSubviews
backgroundColor.frame = self.graphView.bounds
}
viewDidLayoutSubviews should be called when you rotate the device.
If it is not called, override this method and do it as,
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
backgroundColor.frame = self.graphView.bounds
}

Try putting your gradient code into viewDidLayoutSubviews instead of viewDidLoad
When viewDidLoad is called the views are not laid out (ie do not have their final frames set yet), so this is why you are only seeing a partial coverage of the gradient

Related

Edit a UITextView in a ScrollView swift

I've been looking for an answer for a while to this basic problem but can't find anything to solve it.
I have a UITextView in a UIScrollVIew, but I can't edit the text of my textView, it means when I run the project and click on the textView the keyboard does not show up and I can't edit the text.
I tried different configuration but it seems that none works.
My code :
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var imageView2: UIImageView!
var scrollView: UIScrollView!
var textView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
imageView2 = UIImageView(image: UIImage(named: "image.png"))
scrollView = UIScrollView(frame: view.bounds)
scrollView.contentSize = imageView2.bounds.size
scrollView.isUserInteractionEnabled = true
scrollView.isExclusiveTouch = true
scrollView.canCancelContentTouches = true
self.scrollView.delegate = self
view.addSubview(scrollView)
scrollView.addSubview(imageView2)
textView = UITextView(frame: CGRect(x:24,y: 100,width: 340,height: 290))
textView.backgroundColor = UIColor(red: 0.00, green: 1.00, blue: 0.00, alpha: 1.00)
textView.text = "bla bla bla"
imageView2.addSubview(textView)
I will probably be ashamed when I will get the answer.. I guess it's something easy but can't seem to find it. I enabled the user to interact, I thought it would be enough, which is not.
Thanks
you need to add textview in imageview before adding it into scroll view.

Swift: Text label not updating

I have a text label (NSTextField). I want it to pin to the upper left vertex of every object to show an element's number in UI.
But my label shows only ZERO and doesn't move (it stays in the upper left corner). What I did wrong?
Here is my code in AppDelegate.swift:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var drawingView: DrawingView!
#IBOutlet weak var label: NSTextField!
var localArray: [CGPoint] = []
func applicationDidFinishLaunching(aNotification: NSNotification) {
label.textColor = NSColor(calibratedRed: 0.15, green: 0, blue: 0.75, alpha: 0.3)
label.font! = NSFont(name: "Arial Bold", size: 60)!
label.backgroundColor = NSColor.clearColor()
label.stringValue = "\(localArray.count/4)"
for (pointIndex, _) in localArray.enumerate() {
let point = CGPoint(x: (localArray[(pointIndex/4)+1].x), y: (localArray[(pointIndex/4)+1].y))
label.sizeToFit()
label.frame = CGRect(origin: point, size: CGSize(width: label.bounds.width, height: label.bounds.height))
}
}
And here is my code in DrawingView.swift:
var delegate = NSApplication.sharedApplication().delegate as! AppDelegate
delegate.localArray = myArray.map { return $0.coordSequence()[0] }
myArray contains cgpoints.
Am I right putting label parameters inside applicationDidFinishLaunching method?
You are trying to update NSTextField probably from wrong thread.
dispatch_async(dispatch_get_main_queue(),{
label.stringValue = "\((localArray.count/4)+1)"
})
Try to investigate you're array i could make assumption that when you set value of the label localArray.count is 0. Also keep in mind that you are working with Int and if localArray.count is 3 so 3/4 will be 0.

Swift: Gradient splits on rotation

In short, I have a gradient that is a mix of Dark Blue and Black. The gradient looks beautiful, however when I rotate the screen and put it in landscape, the two colors split and half of the screen has a blue background and the other half is black. Figuring I didn't do it right, I copied codes from these two sources:
YouTube video https://www.youtube.com/watch?v=pabNgxzEaRk
Website http://blog.apoorvmote.com/gradient-background-uiview-ios-swift/
This is my code:
let topColor = UIColor(red: 28/255.0, green: 25/255.0, blue: 127/255.0, alpha: 1)
let bottomColor = UIColor(red: 0/255.0, green: 0/255.0, blue: 25/255.0, alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, atIndex: 0)
Can someone point me in the right direction to stop my gradient from splitting in two?
Keep in mind that when changing screen orientation all view's bounds change as well (assuming you are using auto layout) but the same thing is not applied on programmatically added layers. So in your case gradient layer still has the frame which is set based on old view bounds before rotation. To solve this I suggest subclassing your gradient view and update gradient layer frame in layoutSubviews function which is called on each view bounds change.
class GradientView: UIView {
let gradientLayer = CAGradientLayer()
override func awakeFromNib() {
gradientLayer.frame = self.bounds
gradientLayer.colors = yourColors
gradientLayer.locations = yourLocations
self.layer.addSublayer(gradientLayer)
}
override func layoutSubviews() {
gradientLayer.frame = self.bounds
}
}
You only need to override layoutSubview method to Autoresize your gradient view with respect to your device's view
Write only,
override func viewDidLayoutSubviews() {
gradientLayer.frame = self.view.bounds
}
I tried this and its working fine. Hope this help you.

Swift function not drawing circle in updated view

I have a stepper in my View Controller that updates variables(redd1, greenn1, bluee1) in my UIView. drawRect draws an initial circle and updateColor is meant to draw a new circle on top of it with an updated color. The variables get updated when I call updateColor and I know that they get passed through because when i have their values printed out in updateColor they are correct. updateColor won't draw a new circle.
class UIView1: UIView {
var redd1 = 0.0;
var greenn1 = 0.0;
var bluee1 = 0.0;
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect)
{
let circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
func updateColor()
{
let circle = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height: 100.0))
circle.layer.cornerRadius = 50.0;
let startingColor = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat(greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle.backgroundColor = startingColor;
addSubview(circle);
}
}
Do not call addSubview in drawRect. Only use drawRect if you were going to draw a circle yourself (e.g. by calling stroke of a UIBezierPath). Or if you're going to add circles as subviews (as CAShapeLayer sublayers to the view's layer), the retire drawRect altogether.
But by calling addSubview in updateColor, you're not changing the color, but rather you're adding another subview every time. Likewise, by calling addSubview in drawRect, you're adding another subview there, too, every time the OS calls drawRect. For example, I ran your code, changing the color from black to red, to green, to blue, and triggered drawRect to be called again a few more times, and when I look at the view hierarchy in the view debugger, you can see all of the views in the view hierarchy:
This is a dangerous practice, because those subviews will add up over time, taking up memory.
If you're going to add subviews, I would suggest (a) drawRect is not the right place to be adding subviews; and (b) if you're determined to go the addSubview approach, decide whether you can remove the previous subviews before adding new ones.
Personally, if I wanted drawRect to draw circles of different colors, I would just stroke the UIBezierPath. For example:
class CircleView: UIView {
var redd1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var greenn1 : CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var bluee1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
let color = UIColor(red: redd1 / 255.0, green: greenn1 / 255.0, blue: bluee1 / 255.0, alpha: 1.0)
color.setStroke()
let path = UIBezierPath(arcCenter: CGPoint(x: 75, y: 75), radius: 50, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
path.lineWidth = 2
path.lineCapStyle = .Round
path.stroke()
}
}
By the way, you notice that we can retire updateColor entirely. With the above code, if you set either red, green, or blue, it will call setNeedsDisplay, which will trigger the view being redrawn automatically.
You report that the above isn't working. Well, I tried it with this code:
class ViewController: UIViewController {
#IBOutlet weak var circleView: CircleView!
#IBOutlet weak var redSlider: UISlider!
#IBOutlet weak var greenSlider: UISlider!
#IBOutlet weak var blueSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
redSlider.value = Float(circleView.redd1) / 255.0
greenSlider.value = Float(circleView.greenn1) / 255.0
blueSlider.value = Float(circleView.bluee1) / 255.0
}
#IBAction func valueChangedForSlider(sender: UISlider) {
circleView.redd1 = 255.0 * CGFloat(redSlider.value)
circleView.greenn1 = 255.0 * CGFloat(greenSlider.value)
circleView.bluee1 = 255.0 * CGFloat(blueSlider.value)
}
}
And it worked fine:
As you mentioned that the both have the values which they should have.
The two circles which you are drawing are exactly on the same postion and both have exactly the same color according to your code snippet. How should they differ? Of course you can't see the two circles when they are having the same color and same position. They are overlayed.

Can you give UIStackView borders?

I'm trying to give 5 separate UIStackViews in my ViewController borders. I gave each of them an IBOutlet and then called them in viewDidLoad to use the layer property but to no avail. Is it not possible to give stack views borders, programatically?
Code:
#IBOutlet weak var stackView1: UIStackView!
#IBOutlet weak var stackView2: UIStackView!
#IBOutlet weak var stackView3: UIStackView!
#IBOutlet weak var stackView4: UIStackView!
#IBOutlet weak var stackView5: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
stackView1.layer.borderWidth = 5
stackView2.layer.borderWidth = 5
stackView3.layer.borderWidth = 5
stackView4.layer.borderWidth = 5
stackView5.layer.borderWidth = 5
}
Unfortunately this can't be done. UIStackView is unusual in that it is a "non-rendering" view which performs layout (using Auto Layout constraints) but does not display itself. It has a layer like all UIViews, but it's ignored.
See the Apple doc under "Managing the Stack View's Appearance":
The UIStackView is a nonrendering subclass of UIView. It does not
provide any user interface of its own. Instead, it just manages the
position and size of its arranged views. As a result, some properties
(like backgroundColor) have no affect on the stack view. Similarly,
you cannot override layerClass, drawRect:, or drawLayer:inContext:
Its possible to do this by having views inside the stack view be the borders. This can be a lot of work and there might be certain situations that either won't work or have to be worked around so it might not be worth the effort. You'll need to nest the stack views so you can provide borders in both the horizontal and vertical directions. In my Bordered Stack Views blog post I go into more detail about this. But basically I have regular views have a background set to the color of my choosing and I give height or width constraints of 1 depending on the direction of the stack view's axis. Here is the full hierarchy of a 2x2 grid built in interface builder:
Resulting in this result:
Here's a link to my github repo of this example so you can see the storyboard file.
You can embed stackView inside a UIView, then set borders of that view (color, width, etc), and then constraint stackView to that UIView like top, left, right, height.
Here's a handy chunk of code I found and use:
extension UIView {
func addTopBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x:0,y: 0, width:self.frame.size.width, height:width)
self.layer.addSublayer(border)
}
func addRightBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: self.frame.size.width - width,y: 0, width:width, height:self.frame.size.height)
self.layer.addSublayer(border)
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x:0, y:self.frame.size.height - width, width:self.frame.size.width, height:width)
self.layer.addSublayer(border)
}
func addLeftBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x:0, y:0, width:width, height:self.frame.size.height)
self.layer.addSublayer(border)
}
func addMiddleBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x:self.frame.size.width/2, y:0, width:width, height:self.frame.size.height)
self.layer.addSublayer(border)
}
}
Simply use on any view like this:
bottomControls.addMiddleBorderWithColor(color: buttonBorderColor, width: 3.0)
Source: How to add only a TOP border on a UIButton?
As indicated by others you cannot do this (for details see the answer by Clafou).
What you can do, however, is embed your stack view in another UIView; making modifications to the layer of the enclosing UIView.
I think the easiest way to do it is by using no more labels or views with hight/width equals one to represent borders , I mean it is even easier than that via making use of SPACING attribute of stack views themselves . Just fill your stack and its substances , then make spacing one for outer vertical stack , also make spacing one for inner horizontal stacks , you get perfect result . Lastly for sake of giving a specific color to borders I maintained this using background view for the outer stckview , it just has same constraint like stack with background color as you wish to borders , idea is when you make spacing the spacing takes color of view behind the stack , that's it :D , kindly check results as in attached image and let me know if anything not clear
I have multiple UIStackViews inside a UIStackView.
I wanted a top and bottom border only for ONE of the UIStackViews in the stack so I added the UIStackView in question to a UIView with the background color set to the color of the top & bottom border color I wanted and replaced the bordered UIStackView in the arrangedSubviews with the UIView.
import UIKit
import Foundation
let goldBorderedUIView = UIView()
lazy var mainStackView: UIStackView =
{
let mainStack = UIStackView(arrangedSubviews: [goldBorderedUIView, stack2, stack3, stack 4])
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.axis = .vertical
mainStack.spacing = 0.5
mainStack.distribution = .fillEqually
return mainStack
}()
func setupBorderdStack() {
NSLayoutConstraint.activate([
borderedStackView.leadingAnchor.constraint(equalTo: goldBorderedUIView.leadingAnchor),
borderedStackView.topAnchor.constraint(equalTo: goldBorderedUIView.topAnchor, constant: 5),
borderedStackView.trailingAnchor.constraint(equalTo: goldBorderedUIView.trailingAnchor),
borderedStackView.bottomAnchor.constraint(equalTo: goldBorderedUIView.bottomAnchor, constant: -5)
])
}
override func viewDidLoad() {
super.viewDidLoad()
setupBorderdStack()
}
use like this
loseWeight.layer.borderColor = UIColor.orange.cgColor
loseWeight.layer.borderWidth = 1
The simplest way I've found to add a border to a UIStackView is to extend the stack view class and then add two layered views: the bottom one being the same size as the stack view, and the one on top that's used mask out the inside of the border, which is slightly smaller.
Here's the extension in Swift 5:
extension UIStackView {
func addBorder(color: UIColor, backgroundColor: UIColor, thickness: CGFloat) {
let insetView = UIView(frame: bounds)
insetView.backgroundColor = backgroundColor
insetView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(insetView, at: 0)
let borderBounds = CGRect(
x: thickness,
y: thickness,
width: frame.size.width - thickness * 2,
height: frame.size.height - thickness * 2)
let borderView = UIView(frame: borderBounds)
borderView.backgroundColor = color
borderView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(borderView, at: 0)
}
}
Then you add the border with a call like this:
myStackView.addBorder(color: .lightGray, backgroundColor: .white, thickness: 2)