I was wondering if it is possible to draw a diagonal line by using UIViews? I have successfully made a straight line (vertical/horizontal) but I can not find a way to rotate the line.
Thank you for your help!
Here is a basic example of a view that contains a diagonal line view in Swift 2. When the view's bounds change, the diagonal is adapted by adjusting it's length and the rotation angle.
import UIKit
import Foundation
class ViewWithDiagonalLine: UIView {
private let line: UIView
private var lengthConstraint: NSLayoutConstraint!
init() {
// Initialize line view
line = UIView()
line.translatesAutoresizingMaskIntoConstraints = false
line.backgroundColor = UIColor.redColor()
super.init(frame: CGRectZero)
clipsToBounds = true // Cut off everything outside the view
// Add and layout the line view
addSubview(line)
// Define line width
line.addConstraint(NSLayoutConstraint(item: line, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 10))
// Set up line length constraint
lengthConstraint = NSLayoutConstraint(item: line, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)
addConstraint(lengthConstraint)
// Center line in view
addConstraint(NSLayoutConstraint(item: line, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: line, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1, constant: 0))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
// Update length constraint and rotation angle
lengthConstraint.constant = sqrt(pow(frame.size.width, 2) + pow(frame.size.height, 2))
line.transform = CGAffineTransformMakeRotation(atan2(frame.size.height, frame.size.width))
}
}
Putting that into a plain view controller for demonstration purposes
class ViewController: UIViewController {
override func viewDidLoad() {
let v = ViewWithDiagonalLine()
v.frame = CGRectMake(100, 100, 100, 200)
v.layer.borderColor = UIColor.blueColor().CGColor
v.layer.borderWidth = 1
view.addSubview(v)
}
}
yields the result
You could rotate the UIView by setting its transform property. You'd want to use CGAffineTransformMakeRotation or CGAffineTransformRotate.
However, you'd probably be better off using a CAShapeLayer and setting its path property to the line you want to draw.
Related
I want to hide a view completely. I am new to ios. But I have great working experience in android.
So in android We can set visibility to gone. and it completely removes the view from layout. Same I want to do in IOS. here is the layout/design example
View 1
View 2 ( it is the one I want to hide and show)
View 3
now when I want to hide the view 2 I want the space of view 2 also vanish from screen and View 1 and View 3 must stick together . and when view 2 is set to visible then it must display in sequence. i.e View 1,2,3
Right now what I am doing is setting view2.ishidden = true but its not working in the way I want.
Please tell me what is equivalent to view.gone of android in IOS. ???
There are a few ways in achieving this but what you are missing here is some key information on how your layout is being set.
I will assume that the 3 views you are having are vertically aligned, are one next to each other and have equal width.
Programmatically what we are looking at from horizontal perspective this is done:
let firstLeading = NSLayoutConstraint(item: view1, attribute: .leading, relatedBy: .equal, toItem: parent, attribute: .leading, multiplier: 1.0, constant: 0.0)
let betweenSecondAndFirst = NSLayoutConstraint(item: view2, attribute: .leading, relatedBy: .equal, toItem: view1, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let secondEqualWidth = NSLayoutConstraint(item: view2, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .width, multiplier: 1.0, constant: 0.0)
let betweenThirdAndSecond = NSLayoutConstraint(item: view3, attribute: .leading, relatedBy: .equal, toItem: view2, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let lastEqualWidth = NSLayoutConstraint(item: view3, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .width, multiplier: 1.0, constant: 0.0) // Note to view1 to make things easier
let lastTrailing = NSLayoutConstraint(item: view3, attribute: .trailing, relatedBy: .equal, toItem: parent, attribute: .trailing, multiplier: 1.0, constant: 0.0)
And when the second is being skipped we may simply disable betweenThirdAndSecond and add betweenThirdAndFirst as:
let betweenThirdAndFirst = NSLayoutConstraint(item: view3, attribute: .leading, relatedBy: .equal, toItem: view1, attribute: .trailing, multiplier: 1.0, constant: 0.0)
You can play with properties on constraints to enable or disable them. Or you can simply use priorities and toggle them for instance from 900 to 100. You can actually setup all of these constraints in storyboard and then drag the 2 as outlets into your code. Then simply have:
func setMiddleViewShown(_ shown: Bool) {
UIView.animate(withDuration: 0.3) {
self.betweenThirdAndSecond.priority = UILayoutPriority(rawValue: shown ? 900.0 : 100.0)
self.betweenThirdAndFirst.priority = UILayoutPriority(rawValue: shown ? 100.0 : 900.0)
self.view2.alpha = shown ? 1.0 : 0.0
parent.layoutIfNeeded() // parent is most likely "self.view"
}
}
This way is probably best from what you can control (mostly animations). But you may as well use UIStackView which has methods to insert or remove views. UICollectionView should work as well. Or you know.. just do it all programmatically, ignore constraints and simply set frame for each of the views.
A minimum I can think of in using UIStackView is the following and it works:
class ViewController: UIViewController {
let views: [UIView] = [
{ let view = UIView(); view.backgroundColor = .red; return view; }(),
{ let view = UIView(); view.backgroundColor = .green; return view; }(),
{ let view = UIView(); view.backgroundColor = .blue; return view; }()
]
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: CGRect(x: 50.0, y: 100.0, width: 300.0, height: 200.0))
self.view.addSubview(stackView)
stackView.backgroundColor = .black
stackView.distribution = .fillEqually
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
stackView.addArrangedSubview(views[0])
stackView.addArrangedSubview(views[1])
stackView.addArrangedSubview(views[2])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
UIView.animate(withDuration: 0.3) {
if self.stackView.arrangedSubviews.count == 3 {
self.views[1].alpha = 0.0
self.stackView.removeArrangedSubview(self.views[1])
} else {
self.views[1].alpha = 1.0
self.stackView.insertArrangedSubview(self.views[1], at: 1)
}
self.stackView.layoutIfNeeded()
}
}
}
I want to create an array of objects that are horizontally cascaded, I have attempted to create a function to reduce the size of my code however, it seems like there might be an NSLayoutConstraint conflict between the objects that are created maybe?
Here's my code
private func createProfileImageContainers(numberOfFriends: Int) {
for friends in 1...numberOfFriends {
let imageViewContainer = UIView()
imageViewContainer.translatesAutoresizingMaskIntoConstraints = false
imageViewContainer.backgroundColor = UIColor.blue
imageViewContainer.frame = CGRect(x: 0, y: 0, width: frame.width / 10, height: frame.width / 10)
NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: CGFloat((1 / 2) + ((friends - 1) / 50 )), constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .centerY, relatedBy: .equal, toItem: container, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
addSubview(imageViewContainer)
}
}
Here's what debugger is saying
A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs.'
Any suggestions?
EDIT:
Thanks to Robs answer I was able to solve the issues with the debugger however only one instance of imageViewContainer is being adding. Probably because they are all being added to the view hierarchy with the same name so each new view takes the place of the last...
I thought creating a class would solve this but now I can't get anything to appear.
Here's the updated code...
class profileImageContainer: UIView {
let imageViewContainer: UIView = {
let iv = UIView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.backgroundColor = UIColor.blue
return iv
}()
}
private func createProfileImageContainers(numberOfFriends: Int) {
for friends in 1...numberOfFriends {
print(friends)
let imageViewContainer = profileImageContainer()
addSubview(imageViewContainer)
NSLayoutConstraint(item: imageViewContainer, attribute: .width, relatedBy: .equal, toItem: container, attribute: .width, multiplier: 0.1, constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .height, relatedBy: .equal, toItem: container, attribute: .width, multiplier: 0.1, constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: 0.5 + (CGFloat(friends - 1) / 50.0), constant: 0).isActive = true
NSLayoutConstraint(item: imageViewContainer, attribute: .centerY, relatedBy: .equal, toItem: container, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
}
}
One problem is the expression:
CGFloat((1 / 2) + ((friends - 1) / 50))
That is doing integer division and then converting the resulting integer into a CGFloat. In practice, your expression will return 0 for the first 50 friends values.
You want to do floating point math, converting friends - 1 to a CGFloat before you do the division:
0.5 + CGFloat(friends - 1) / 50.0
I'd also suggest that you'll want to add the subview before adding the constraints.
You should specify the width and height constraints and eliminate the setting of the frame. The frame will be discarded when the constraints are applied and in the absence of width and height constraints, the constraints are ambiguous.
There are a couple of problems with your second code sample:
Your ProfileImageContainer has an imageViewContainer, but you never do anything with it. So, you're not going to see your ProfileImageContainers (because you didn't set its own backgroundColor). I can imagine that you might eventually do something meaningful with imageViewContainer property of ProfileImageContainer (e.g. add it to the view hierarchy, set the image, etc.). But for now, I'd suggest you remove that as it's only confusing the situation.
Even when we fix the above, the subviews are going to overlap because you've defined them to be 1/10th of the width of some container, but you're adjusting the centerX multiplier by 1/50th.
The net effect of this is that the views will overlap, making it appear that there is only one present. But I believe if you use the view debugger, you’ll see that they’re all there. You need to alter the centerX constraint so that they don’t overlap.
Anyway, here is a rendition that fixes the above issues:
// SampleView.swift
import UIKit
class ProfileImageContainer: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .blue
}
}
class SampleView: UIView {
var container: UIView!
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
addSubview(container)
NSLayoutConstraint.activate([
container.leftAnchor.constraint(equalTo: leftAnchor),
container.rightAnchor.constraint(equalTo: rightAnchor),
container.topAnchor.constraint(equalTo: topAnchor),
container.bottomAnchor.constraint(equalTo: bottomAnchor)
])
createProfileImageContainers(numberOfFriends: 5)
}
var friends = [UIView]()
private func createProfileImageContainers(numberOfFriends: Int) {
// remove old friends in case you called this before
friends.forEach { $0.removeFromSuperview() }
friends.removeAll()
// now add friends
for friend in 0 ..< numberOfFriends { // easier to go from 0 to numberOfFriends-1 than subtract one later
print(friend)
let imageViewContainer = ProfileImageContainer()
container.addSubview(imageViewContainer)
NSLayoutConstraint.activate([
imageViewContainer.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.1),
imageViewContainer.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.1),
NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: 2 * CGFloat(friend + 1) / CGFloat(numberOfFriends + 1), constant: 0),
imageViewContainer.centerYAnchor.constraint(equalTo: container.centerYAnchor)
])
friends.append(imageViewContainer)
}
}
}
Summary:
I want to implement my own UIView object with specific layout constraints utilizing Swift 3. I'm not sure how to configure the layout constraints in the object I'm customizing. I was thinking of passing in a reference to the super view to append the layout constraints onto.
Question:
How can I implement my own custom user interface object while also maintaining that objects layout constraints in the model instead of the view?
Code:
Custom View:
import Foundation
import UIKit
class CustomView: UIView {
var header: UIView?
var users: [Array<OtherObject>]?
init() {
super.init(frame: UIScreen.main.bounds);
//for debug validation
print("My Custom Init");
return;
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented");
}
func configureTitleHeader(){
header = UIView()
}
func configureConstraints(superView: UIView){
self.configureTitleHeader()
let titleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.header!, attribute: .left, relatedBy: .equal, toItem: superView, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.header!, attribute: .right, relatedBy: .equal, toItem: superView, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.header!, attribute: .top, relatedBy: .equal, toItem: superView, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.header!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
]
superView.addConstraints(titleConstraints)
}
}
UINavigationController:
override func viewDidLoad() {
super.viewDidLoad()
let customView = CustomView()
customView.configureConstraints(superView: self.view)
self.view.addSubview(customView)
}
I am getting the following error :
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout)
Basically I want to have a blue view that is 200h x 200w that is centered in the middle of the screen.
UIViewController.swift
override func loadView() {
super.loadView()
self.view = View()
}
Meanwhile in the View.swift
class View: UIView {
var blueView : UIView?
convenience init() {
// also tried UIScreen.mainScreen().bounds
self.init(frame:CGRectZero)
self.backgroundColor = UIColor.redColor()
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init:coder")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
func setupView() {
self.blueView = UIView()
self.blueView?.backgroundColor = UIColor.blueColor()
self.blueView?.translatesAutoresizingMaskIntoConstraints = false
// self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.blueView!)
let centerXConstraint = NSLayoutConstraint(
// object that we want to constrain
item: self.blueView!,
// the attribute of the item we want to constraint
attribute: NSLayoutAttribute.CenterX,
// how we want to relate this item with another item so most likely its parent view
relatedBy: NSLayoutRelation.Equal,
// this is the item that we are setting up the relationship with
toItem: self,
attribute: NSLayoutAttribute.CenterX,
// How much I want the CenterX of BlueView to Differ from the CenterX of the self
multiplier: 1.0,
constant: 0)
let centerYConstraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: self,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0)
// These work but the previous two don't
let widthContraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.Width,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0,
constant: 200)
let heightConstraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0,
constant: 200)
self.blueView?.addConstraints([widthContraint, heightConstraint, centerXConstraint, centerYConstraint])
}
Height and width constraints belong to the view they pertain to. Centering and positioning belong to that view's parent so you must add these two to the parent, not the view itself.
I have already created a circular button which is a custom UIView.Here's the code:
class HelpTips: UIView {
weak var hotSpot: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
let strongHotSpot = UIButton()
hotSpot = strongHotSpot
self.addSubview(strongHotSpot)
hotSpotOne.translatesAutoresizingMaskIntoConstraints = false
hotSpotOne.backgroundColor = UIColor.TRLMHelpTipYellowColor()
hotSpotOne.layer.borderColor = UIColor.TRLMHelpTipStrokeColor().CGColor
hotSpotOne.layer.borderWidth = 1
let horizontalConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: -1)
let verticalConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 16)
let widthConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 40)
let heightConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 40)
self.addConstraints([verticalConstraint, horizontalConstraint, widthConstraint, heightConstraint])
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Now this same button is used throughout the app at several places but it's placed at different positions. So each View Controller will make use of that UIView.
So technically the look of the button remains the same but the constraints for that button keep on changing depending on it's position. I want to follow DRY(Don't repeat yourself) technique here.
I have done this kind of thing before but the code was being repeated several times and was not efficient. How to go about this?
Any help is appreciated. Thanks!
You could create a custom navigation controller and store the view as a property in it. Every view controller will have access to the navigation controller, so they can just reference that property and time you need to use the view. Should keep it DRY.
Create a custom view using an xib. Add your UIButton as subview.
This tutorial will be helpful.