View with multiple layers dont relese memory - ios

I create custom View:
final class Clock: UIView {
lazy var hourArrow: CALayer = {
let layer = CALayer()
layer.contents = #imageLiteral(resourceName: "hourArrow").cgImage
layer.contentsGravity = kCAGravityResizeAspect
return layer
}()
lazy var subLayers = [hourArrow,minuteArrow,centerDial,clockDial,clockArrow,block]
override init(frame: CGRect) {
super.init(frame: frame)
subLayers.forEach { (l) in
layer.addSublayer(l)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
subLayers.forEach({ l in
let rect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
l.frame = rect
})
}
}
and i use it ViewController:
class ClockViewController: UIViewController {
lazy var clock: Clock = {
let clock = Clock(frame: .zero)
clock.translatesAutoresizingMaskIntoConstraints = false
return clock
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(clock)
let safeArea = view.safeAreaLayoutGuide
clock.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 40).isActive = true
clock.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 40).isActive = true
clock.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -40).isActive = true
clock.heightAnchor.constraint(equalToConstant: 100).isActive = true
// Do any additional setup after loading the view.
}
}
then i move to this view controller, memory up to 10 MB with this layers.
When i dismiss VC, memory don't release but deinit was called.
(I must remove CALayer variables to post this, all of layers are create like first)
Any Ideas?

Related

How to add gravity to UIViews in a UITableViewCell?

I am trying to animate a few UIViews by having gravity act on them. Ideally, each view would stay inside the UITableViewCell bounds and they would bounce off of each other and the walls like boundaries. However, when I run this code, the UIView's just kind of float in place, only moving if another view happens to populate in the same spot.
class AnimateTableViewCell: UITableViewCell {
private var animator: UIDynamicAnimator!
private var gravity: UIGravityBehavior!
private var collision: UICollisionBehavior!
private var views = [UIView]()
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
animator = UIDynamicAnimator(referenceView: self.contentView)
let items = [UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill")]
for item in items {
let size = Int.random(in: 75 ... 180)
self.setUpView(view: UIView(frame: CGRect(x: 100, y: 100, width: size, height: size)), image: item!, size: size/2)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func applyGravity(to item: UIView) {
gravity = UIGravityBehavior(items: [item])
animator.addBehavior(gravity)
collision = UICollisionBehavior(items: views)
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)
let bounce = UIDynamicItemBehavior(items: views)
bounce.elasticity = 0.3
}
private func setUpView(view: UIView, image: UIImage, size: Int) {
contentView.addSubview(view)
view.backgroundColor = image.averageColor
view.layer.cornerRadius = view.frame.height/2
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: CGFloat(size)),
imageView.widthAnchor.constraint(equalToConstant: CGFloat(size)),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
views.append(view)
applyGravity(to: view)
}
}
There are two main problems.
First, the collision behavior is getting in your way. Comment out this line:
animator.addBehavior(collision)
Now at least you will see the gravity operate.
Second, you are saying
gravity = UIGravityBehavior(items: [item])
animator.addBehavior(gravity)
multiple times, once per item. That's wrong. You must have just one gravity behavior that embraces all the items.
I got a fair-to-middling result just by rearranging some of your code and changing a few of the magic numbers; this should get you started, at least (I used a table view with just one cell, quite tall in height):
class AnimateTableViewCell: UITableViewCell {
private var animator: UIDynamicAnimator!
private var gravity = UIGravityBehavior()
private var collision = UICollisionBehavior()
private var views = [UIView]()
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
animator = UIDynamicAnimator(referenceView: self.contentView)
let items = [UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill"),UIImage(systemName: "star.fill")]
for item in items {
let size = Int.random(in: 20...40)
self.setUpView(view: UIView(frame: CGRect(x: 100, y: 100, width: size, height: size)), image: item!, size: size)
}
animator.addBehavior(gravity)
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)
let bounce = UIDynamicItemBehavior(items: views)
bounce.elasticity = 0.3
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func applyGravity(to item: UIView) {
gravity.addItem(item)
collision.addItem(item)
}
private func setUpView(view: UIView, image: UIImage, size: Int) {
contentView.addSubview(view)
view.backgroundColor = .red
view.layer.cornerRadius = view.frame.height/2
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: CGFloat(size)),
imageView.widthAnchor.constraint(equalToConstant: CGFloat(size)),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
views.append(view)
applyGravity(to: view)
}
}

how to remove safe area from UIView Programmatically in Swift?

This is a custom view, this view creates a square with a given frame with the background color. I am adding a custom view to a subview, the view appears properly. But I am not able to cover the bottom safe area, anyone can help me to remove the safe area from bottom Programmatically.
class CustomView: UIView {
override var frame: CGRect {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7).setFill()
UIRectFill(rect)
let square = UIBezierPath(rect: CGRect(x: 200, y: rect.size.height/2 - 150/2, width: UIScreen.main.bounds.size.width - 8, height: 150))
let dashPattern : [CGFloat] = [10, 4]
square.setLineDash(dashPattern, count: 2, phase: 0)
UIColor.white.setStroke()
square.lineWidth = 5
square.stroke()
}
}
Consider the following example:
class ViewController: UIViewController {
private let myView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
configureCustomView()
}
private func configureCustomView() {
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.backgroundColor = .systemPurple
NSLayoutConstraint.activate([
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
myView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
myView.heightAnchor.constraint(equalToConstant: 200)
])
}
}
Result:
If you don't want to go over the safe area, then you could use myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) inside NSLayoutConstraint.activate([...]).
So you actually don't have to remove the SafeArea, because you can simply ignore them if you want so...
In case you want to do this fast. (Not programatically)
Open storyboard.
Select your UIView.
Safe Area should be unselected.

Hiding/Showing Input Accessory View

I have a custom inputAccessoryView and am trying to toggle hiding/showing it. I don't want to utilize .isHidden or .removeFromSuperView(), rather, use the bottom slide in/out, which seems to be native as I present other viewControllers onto the hierarchy and this animation executes.
I've tried to resignFirstResponder with no luck and there doesn't seem to be any existing commentary around this. Any thoughts would be appreciated as I am admittedly stumped.
class CustomInputAccessoryView: UIView {
let customTextView: CustomInputTextView = {
let tv = CustomInputTextView()
tv.isScrollEnabled = false
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = .flexibleHeight
addSubview(customTextView)
customTextView.topAnchor.constraint(equalTo: topAnchor, constant: 12).isActive = true
customTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: 8).isActive = true
customTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
customTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: 10).isActive = true
customTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomInputTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
override var canResignFirstResponder: Bool {
return true
}
override var canBecomeFirstResponder: Bool {
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder has not been implemented")
}
}
//in viewcontroller
lazy var inputContainerView: CustomInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60)
let customInputAccessoryView = CustomInputAccessoryView(frame: frame)
return customInputAccessoryView
}()
//onLoad
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
I don't know if this is really what you're going for, but give it a try.
Tapping anywhere in the view will show/hide the input accessory view:
class TestInputViewController: UIViewController {
//in viewcontroller
lazy var inputContainerView: CustomInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60)
let customInputAccessoryView = CustomInputAccessoryView(frame: frame)
return customInputAccessoryView
}()
override var canBecomeFirstResponder: Bool {
return true
}
//onLoad
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
override func viewDidLoad() {
super.viewDidLoad()
// so we can see the frame
inputContainerView.backgroundColor = .blue
// tap anywhere in view to show / hide input accessory view
let g = UITapGestureRecognizer(target: self, action: #selector(didTap(sender:)))
view.addGestureRecognizer(g)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#objc func didTap(sender: UITapGestureRecognizer) {
if self.isFirstResponder {
resignFirstResponder()
} else {
becomeFirstResponder()
}
}
}
class CustomInputAccessoryView: UIView {
let customTextView: CustomInputTextView = {
let tv = CustomInputTextView()
tv.isScrollEnabled = false
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = .flexibleHeight
addSubview(customTextView)
customTextView.translatesAutoresizingMaskIntoConstraints = false
customTextView.topAnchor.constraint(equalTo: topAnchor, constant: 12).isActive = true
customTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8).isActive = true
customTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
customTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: -10).isActive = true
customTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomInputTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
override var canResignFirstResponder: Bool {
return true
}
override var canBecomeFirstResponder: Bool {
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder has not been implemented")
}
}
Not related to showing / hiding, but a few of your constraints were wrong, causing the text view to be misplaced.

How to change color only a fraction of the total in swift?

I'm doing a QR code scan. I use UIView to change the background color of the screen to translucent color. However, I want to make the background color of the place that scans the QR code transparent. What should I do?
class QRcodeScannerViewController : UIViewController, AVCaptureMetadataOutputObjectsDelegate {
#IBOutlet weak var qrcodeView: UIView!
#IBOutlet weak var header: UINavigationBar!
#IBOutlet weak var flash: UIButton!
#IBOutlet weak var qrcodeScanArea: UIImageView!
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
self.qrcodeView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
view.layer.insertSublayer(previewLayer, at: 0)
view.bringSubviewToFront(flash)
view.bringSubviewToFront(header)
header.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
header.isTranslucent = true
header.backgroundColor = UIColor.clear
header.shadowImage = UIImage()
Current My QRcode ScanView
Here's the view I want:
I don't know which part I reverse, but only the color I want is black and the rest is transparent.
view.addSubview(qrcodeView)
let shape = CGRect(x: 0, y: 0, width: 200, height: 200)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: shape, cornerRadius: 0).cgPath
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
qrcodeView.layer.mask = maskLayer
I made and applied the class by referring to the answers below. But it doesn't work for me.
let focusview = FocusView.init(frame: qrcodeView.frame)
focusview.areaBackground = UIColor.black
view.addSubview(focusview)
I did something similar ... actually pretty much the same thing, some time ago.
I've dug out the code I used and have posted it here for your reference
fileprivate let focusSize: CGSize = CGSize(width: 218, height: 150)
fileprivate let focusCornerRadius: CGFloat = 10.0
class FocusView: UIView {
var areaBackground: UIColor? {
set {
maskedFocusView.backgroundColor = newValue
}
get {
return maskedFocusView.backgroundColor
}
}
fileprivate let maskedFocusView: MaskedFocusView = {
return MaskedFocusView()
}()
let focusView: UIView = {
let view = UIView()
view.layer.borderColor = UIColor.white.cgColor
view.layer.borderWidth = 2
view.layer.cornerRadius = focusCornerRadius
// view.layer.shadowColor = UIColor.white.cgColor
// view.layer.shadowRadius = 5.0
// view.layer.shadowOpacity = 0.9
// view.layer.shadowOffset = CGSize.zero
view.layer.masksToBounds = true
return view
}()
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
backgroundColor = UIColor.darkGray.withAlphaComponent(0.5)
translatesAutoresizingMaskIntoConstraints = false
addSubview(maskedFocusView)
addSubview(focusView)
setupFocusViewConstraints()
setupMaskedFocusViewConstraints()
}
func setupFocusViewConstraints() {
NSLayoutConstraint.activate(
focusView.centerXAnchor.constraint(equalTo: centerXAnchor),
focusView.centerYAnchor.constraint(equalTo: centerYAnchor)
)
let regularFocusViewConstraints = [
focusView.widthAnchor.constraint(equalToConstant: focusSize.width),
focusView.heightAnchor.constraint(equalToConstant: focusSize.height)
]
NSLayoutConstraint.activate(regularFocusViewConstraints)
}
func setupMaskedFocusViewConstraints() {
NSLayoutConstraint.activate(
maskedFocusView.centerXAnchor.constraint(equalTo: centerXAnchor),
maskedFocusView.centerYAnchor.constraint(equalTo: centerYAnchor),
maskedFocusView.topAnchor.constraint(equalTo: topAnchor),
maskedFocusView.bottomAnchor.constraint(equalTo: bottomAnchor),
maskedFocusView.leadingAnchor.constraint(equalTo: leadingAnchor),
maskedFocusView.trailingAnchor.constraint(equalTo: trailingAnchor)
)
}
}
// MARK: - Masked focus view
fileprivate class MaskedFocusView: UIView {
let maskLayer: CAShapeLayer = CAShapeLayer()
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
backgroundColor = UIColor.darkGray.withAlphaComponent(0.5)
maskLayer.backgroundColor = UIColor.clear.cgColor
layer.mask = maskLayer
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
let width = bounds.width
let height = bounds.height
let x = (width - focusSize.width) / 2
let y = (height - focusSize.height) / 2
let focusRect = CGRect(x: x, y: y, width: focusSize.width, height: focusSize.height)
let fullRect = CGRect(origin: bounds.origin, size: bounds.size)
let path = CGMutablePath()
path.addPath(UIBezierPath(rect: fullRect).cgPath)
path.addPath(UIBezierPath(roundedRect: focusRect, cornerRadius: focusCornerRadius).reversing().cgPath)
maskLayer.path = path
maskLayer.fillRule = .evenOdd
}
}
// MARK: - Layout constraints extension
extension NSLayoutConstraint {
/// A helper function to activate layout constraints.
static func activate(_ constraints: NSLayoutConstraint? ...) {
for case let constraint in constraints {
guard let constraint = constraint else {
continue
}
(constraint.firstItem as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
constraint.isActive = true
}
}
}
extension Array where Element: NSLayoutConstraint {
func activate() {
forEach {
if !$0.isActive {
$0.isActive = true
}
}
}
func deactivate() {
forEach {
if $0.isActive {
$0.isActive = false
}
}
}
}

Swift: Custom view hierarchy

I am attempting to build a cinema ticketing app and I need help with my view hierarchy. At the moment my cinemaView does not get added to the VC.
Here is how I attempt to do it. I have a custom seatView that has two properties (isVacant and seatNumber) and a custom cinemaView that has [seatView] as a property (well different cinema has different seatings). My code as such:
//In my viewController
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let cinema: CinemaView = {
let v = CinemaView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.zoomScale = scrollView.minimumZoomScale
scrollView.delegate = self
scrollView.isScrollEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
let seat1 = SeatView()
seat1.isVacant = false
seat1.seatNumber = "2A"
let seat2 = SeatView()
seat2.isVacant = true
seat2.seatNumber = "3B"
cinema.seats = [seat1, seat2]
view.addSubview(scrollView)
scrollView.addSubview(cinema)
let views = ["scrollView": scrollView, "v": cinema]
let screenHeight = view.frame.height
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView(\(screenHeight / 2))]", options: NSLayoutFormatOptions(), metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-60-[v(50)]", options: NSLayoutFormatOptions(), metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
}
//At my custom cinemaView
class CinemaView: UIView {
var seats = [SeatView]()
var xPos: Int = 0
let cinemaView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(cinemaView)
cinemaView.backgroundColor = .black
let views = ["v": cinemaView]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
for seat in seats {
cinemaView.addSubview(seat)
seat.frame = CGRect(x: xPos, y: 0, width: 20, height: 20)
xPos += 8
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//At my custom seatView
class SeatView: UIView {
var isVacant: Bool?
var seatNumber: String?
let seatView: UIView = {
let v = UIView()
v.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
v.layer.cornerRadius = 5
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(seatView)
seatView.backgroundColor = setupBackgroundColor()
}
func setupBackgroundColor() -> UIColor {
if let isVacant = isVacant {
if isVacant {
return UIColor.green
} else {
return UIColor.black
}
} else {
return UIColor.yellow
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
My code does not seem to add the cinemaView to my VC. Could anyone point me where have I gone wrong? Or perhaps even advice if this method is suitable for this application? Thanks.
You need to give the frames while creating any UIView.
override init(frame: CGRect) will be called when you specify the frame of your custom UIView.
I have created a similar hierarchy as yours as an example. Have a look at it.
Also xPos must be such that it does not overlap the previous SeatView, i.e (new xPos + previous SeatView width).
Also in your SeatView and CinemaView you are adding a UIView inside another UIView which is kind of redundant. You don't need to do that.
Example:
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
let seat1 = SeatView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let seat2 = SeatView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let cinema = CinemaView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
cinema.seats = [seat1, seat2]
self.view.addSubview(cinema)
}
}
class CinemaView: UIView
{
var seats = [SeatView](){
didSet{
for seat in seats
{
seat.frame.origin.x = xPos
xPos += 100
self.addSubview(seat)
}
}
}
var xPos: CGFloat = 0
override init(frame: CGRect)
{
super.init(frame: frame)
self.backgroundColor = .black
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
class SeatView: UIView
{
override init(frame: CGRect)
{
super.init(frame: frame)
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
Well, you are doing several things wrong here...
You are subclassing UIView, but in your custom view you are adding a UIView as a subview, and trying to treat that as your actual view.
In your CinemaView class, you are looping your array of "seats" ... but you are doing so before assigning the array, so no seats will ever be created.
Similarly, in SeatView, you are trying to set the background color based on the .isVacant property, but you are doing so before the property is set.
You are trying to use a UIScrollView but you do not have the constraints set up correctly.
Setting constraints with Visual Format may feel convenient or easy, but it has limits. In your specific case, you want your scroll view to be 1/2 the height of your main view. With VFL, you have to calculate and explicitly set the constant, which will also have to be re-calculated any time the frame changes. Using the scroll view's .heightAnchor.constraint, setting it equal to the .heightAnchor of the view, and setting multiplier: 0.5 gets you 50% without any calculations.
So, lots to understand and lots to think about. I've made some edits to your original code. This should be considered a "starting point" for you to learn from, not "drop-in finished code":
class CinemaViewController: UIViewController, UIScrollViewDelegate {
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.minimumZoomScale = 1.0
sv.maximumZoomScale = 10.0
sv.zoomScale = sv.minimumZoomScale
sv.isScrollEnabled = true
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
let cinema: CinemaView = {
let v = CinemaView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.black
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// set our background to yellow, so we can see where it is
view.backgroundColor = .yellow
// create 2 "SeatView" objects
let seat1 = SeatView()
seat1.isVacant = false
seat1.seatNumber = "2A"
let seat2 = SeatView()
seat2.isVacant = true
seat2.seatNumber = "3B"
// set the array of "seats" in the cinema view object
cinema.seats = [seat1, seat2]
// assign scroll view delegate
scrollView.delegate = self
// set scroll view background color, so we can see it
scrollView.backgroundColor = .blue
// add the scroll view to this view
view.addSubview(scrollView)
// pin scrollView to top, leading and trailing, with 8-pt padding
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
// set scrollView height to 50% of view height
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
// add cinema view to the scroll view
scrollView.addSubview(cinema)
// pin cinema to top and leading of scrollView, with 8.0-pt padding
cinema.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
cinema.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 8.0).isActive = true
// set the width of cinema to scrollView width -16.0 (leaves 8.0-pts on each side)
cinema.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -16.0).isActive = true
// cinema height set to constant of 60.0 (for now)
cinema.heightAnchor.constraint(equalToConstant: 60.0).isActive = true
// in order to use a scroll view, its .contentSize must be defined
// so, use the trailing and bottom anchor constraints of cinema to define the .contentSize
cinema.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0.0).isActive = true
cinema.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0.0).isActive = true
}
}
//At my custom cinemaView
class CinemaView: UIView {
// when the seats property is set,
// remove any existing seat views
// and add a new seat view for each seat
// Note: eventually, this will likely be done with Stack Views and / or constraints
// rather than calculated Rects
var seats = [SeatView]() {
didSet {
for v in self.subviews {
v.removeFromSuperview()
}
var seatRect = CGRect(x: 0, y: 0, width: 20, height: 20)
for seat in seats {
self.addSubview(seat)
seat.frame = seatRect
seatRect.origin.x += seatRect.size.width + 8
}
}
}
// we're not doing anything on init() - yet...
// un-comment the following if you need to add setup code
// see the similar functionality in SeatView class
/*
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
*/
// perform any common setup tasks here
func commonInit() -> Void {
//
}
}
//At my custom seatView
class SeatView: UIView {
// change the background color when .isVacant property is set
var isVacant: Bool = false {
didSet {
if isVacant {
self.backgroundColor = UIColor.green
} else {
self.backgroundColor = UIColor.red
}
}
}
// not used currently
var seatNumber: String?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
// perform any common setup tasks here
func commonInit() -> Void {
self.layer.cornerRadius = 5
}
}

Resources