I'm having an issue with animating an UIImageView inside an CollectionViewCell. I've set the views with Auto Layout, not sure if that could cause a problem.
If tried calling in didEndDisplaying but with no results.
What is the proper lifecycle function to call an cell animation?
Code:
import UIKit
class ProfileCell: UICollectionViewCell {
....
let backgroundImageView: UIImageView = {
let iv = UIImageView(frame: .zero)
iv.contentMode = .scaleAspectFill
iv.image = UIImage(named: "lustrum2017")
iv.clipsToBounds = true
return iv
}()
var blurView: UIVisualEffectView = {
let be = UIBlurEffect(style: .light)
let vv = UIVisualEffectView(effect: be)
return vv
}()
let profileImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "dummy")
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 40
iv.layer.masksToBounds = true
iv.layer.borderColor = Colors.primaryColor.cgColor
iv.layer.borderWidth = 1.0
iv.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
animateViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func animateViews() {
self.profileImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
UIView.animate(withDuration: 0.45, animations: {
self.layoutIfNeeded()
}, completion: nil)
}
private func setupViews() {
contentView.addSubview(backgroundImageView)
contentView.addSubview(blurView)
contentView.addConstraintsWithFormat("H:|[v0]|", views: backgroundImageView)
contentView.addConstraintsWithFormat("V:|[v0(150)]", views: backgroundImageView)
contentView.addConstraintsWithFormat("H:|[v0]|", views: blurView)
contentView.addConstraintsWithFormat("V:|[v0(150)]", views: blurView)
blurView.addSubview(profileImageView)
blurView.addConstraintsWithFormat("H:|-\(frame.width / 2 - 40)-[v0(80)]-\(frame.width / 2 - 40)-|", views: profileImageView)
blurView.addConstraintsWithFormat("V:|[v0(80)]", views: profileImageView)
}
....
}
You might try doing the transformation inside of the UIView.animate block and it should work. As follows:
private func animateViews() {
self.profileImageView.transform = CGAffineTransform.identity
UIView.animate(withDuration: 0.45, animations: {
self.profileImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
self.layoutIfNeeded()
}, completion: nil)
}
Also, if you created the cell from the storyboard/xib instead of manually registering the cell via code, awakeFromNib might get you the result you want.
Edit: The above answer is assuming that you want the animation to occur once the cell is first created, as it seems more similar to the code you have. If you want to have it be called every time the cell appears on screen then you need to use the UICollectionViewDelegate's collectionView(_:willDisplay:forItemAt:)
Related
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
}
}
}
}
I am implementing loading concept by custom class loading view. I need to use this custom class by using simple self.show() and self.dismiss function. Here, I achieved this concept by using UIActivityController within UIAlertView but I need to do this using custom class.
Below code what I achieved by using UIActivityController within UIAlertView
func loadinHubShow() {
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
}
func loadinHubDismiss() {
dismiss(animated: false, completion: nil)
}
How to change this to custom class loading view. I don’t want to use UIActivityController within UIAlertView, Alternatively need to create same look and feel UI design by UIView.
import Foundation
import UIKit
public class Indicator {
public static let sharedInstance = Indicator()
var blurImg = UIImageView()
var indicator = UIActivityIndicatorView()
private init()
{
blurImg.frame = UIScreen.main.bounds
blurImg.backgroundColor = UIColor.black
blurImg.isUserInteractionEnabled = true
blurImg.alpha = 0.5
indicator.style = .whiteLarge
indicator.center = blurImg.center
indicator.startAnimating()
indicator.color = .red
}
func showIndicator(){
DispatchQueue.main.async( execute: {
UIApplication.shared.keyWindow?.addSubview(self.blurImg)
UIApplication.shared.keyWindow?.addSubview(self.indicator)
})
}
func hideIndicator(){
DispatchQueue.main.async( execute:
{
self.blurImg.removeFromSuperview()
self.indicator.removeFromSuperview()
})
}
}
import UIKit
class Loader: UIView {
var view: UIView!
#IBOutlet fileprivate weak var blurView: UIVisualEffectView!
#IBOutlet fileprivate weak var loader: UIActivityIndicatorView!
var targetView: UIView?
required init(forView view: UIView) {
super.init(frame: view.bounds)
targetView = view
self._setup()
targetView?.addSubview(self)
}
override init(frame: CGRect) {
super.init(frame: frame)
_setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self._setup()
}
private func _setup() {
view = _loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
addSubview(view)
}
private func _loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView
return nibView
}
func showLoading(withCompletion completion: (() -> Swift.Void)? = nil) {
UIView.animate(withDuration: 0.5, animations: {
}) { _ in
completion?()
}
}
func hideLoading() {
UIView.animate(withDuration: 0.5, animations: {
}) { _ in
self.removeFromSuperview()
}
}
}
You can call below functions in your ViewController
func showLoading() {
if self.view.subviews.map({ $0 is Loader }).contains(true) {
return
}
let loader = Loader(forView: self.view)
loader.showLoading()
}
func hideLoading() {
for view in self.view.subviews {
if let view = view as? Loader {
view.hideLoading()
break
}
}
}
You can customize the below code according to your needs
func showActivityIndicatory(uiView: UIView) {
var container: UIView = UIView()
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColorFromHex(0xffffff, alpha: 0.3)
var loadingView: UIView = UIView()
loadingView.frame = CGRectMake(0, 0, 80, 80)
loadingView.center = uiView.center
loadingView.backgroundColor = UIColorFromHex(0x444444, alpha: 0.7)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
var actInd: UIActivityIndicatorView = UIActivityIndicatorView()
actInd.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
actInd.activityIndicatorViewStyle =
UIActivityIndicatorViewStyle.WhiteLarge
actInd.center = CGPointMake(loadingView.frame.size.width / 2,
loadingView.frame.size.height / 2);
loadingView.addSubview(actInd)
container.addSubview(loadingView)
uiView.addSubview(container)
actInd.startAnimating()
}
I'm trying to make a custom alert view and facing some issues with the fact that the displayed view is cutting the bottom half of the view (Images below)
How it's being displayed:
Desired Output:
So basically, I have a XIB called CustomAlertView supported by a class of same name with init as follows:
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("CustomAlertView", owner: self, options: nil)
contentView.frame = self.bounds
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
//contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
I have another class that is responsible for creating an alert, CustomAlert, using the customAlertView. This CustomAlert class is creating the backgroundView and dialogView( which I'm trying to add my customAlertView to it) with the following code:
func initialize(title:String, description:String){
dialogView.clipsToBounds = true
backgroundView.frame = frame
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.6
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTappedOnBackgroundView)))
addSubview(backgroundView)
dialogView.frame.origin = CGPoint(x: 0, y: 0)
dialogView.frame.size = CGSize(width: frame.width-32, height: frame.height/3)
dialogView.backgroundColor = UIColor.white
dialogView.layer.cornerRadius = 6
let alertView = CustomAlertView.init(frame: self.bounds)
alertView.titleLabel.text = title
alertView.descriptionLabel.text = description
alertView.cancelButton.backgroundColor = UIColor.brown
dialogView.addSubview(alertView)
addSubview(dialogView)
}
I believe that I'm making a confusion with the frames and bounds but couldn't find a solution.
I'd like the desired output to be placed perfectly inside the dialogView.
EDIT
Code for my .show function in CustomAlert
func show(animated:Bool){
self.backgroundView.alpha = 0
self.dialogView.center = CGPoint(x: self.center.x, y: self.frame.height + self.dialogView.frame.height/2)
UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(self)
if animated {
UIView.animate(withDuration: 0.33, animations: {
self.backgroundView.alpha = 0.66
})
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: {
self.dialogView.center = self.center
}, completion: { (completed) in
})
}else{
self.backgroundView.alpha = 0.66
self.dialogView.center = self.center
}
}
Github link git-alert-view
For anyone facing the same difficulties as me, I was able to accomplish the wanted result.
I used AutoLayouts as suggested by #HAK. But instead of writing my own NSLayoutConstraint I used roberthein library called TinyConstraints.
Basically, I used as follow:
Instead of
NSLayoutConstraint.activate([
alertView.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0),
alertView.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0),
alertView.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0),
alertView.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant:
0)])
with TinyConstraints:
alertView.edges(to: superview)
That´s it
Change your CustomAlertView like this:
class CustomAlertView: UIView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var confirmButton: UIButton!
#IBOutlet weak var cancelButton: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
static func customAlert() -> CustomAlertView {
return Bundle.main.loadNibNamed("CustomAlertView", owner: self, options: nil)!.first as! CustomAlertView
}
}
Your CustomAlert's initialize method like this:
func initialize(title:String, description:String){
dialogView.clipsToBounds = true
backgroundView.frame = frame
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.6
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTappedOnBackgroundView)))
addSubview(backgroundView)
dialogView.frame.origin = CGPoint(x: 0, y: 0)
dialogView.frame.size = CGSize(width: frame.width-32, height: frame.height/3)
dialogView.backgroundColor = UIColor.white
dialogView.layer.cornerRadius = 6
let alertView = CustomAlertView.customAlert()
alertView.titleLabel.text = title
alertView.descriptionLabel.text = description
alertView.cancelButton.backgroundColor = UIColor.brown
dialogView.addSubview(alertView)
addSubview(dialogView)
}
In the CustomAlertView xib:
1. Select fileowner and remove the class (default to NSObject).
2. Select fileowner and then remove all the outlets.
3. Select your content view and give it class = CustomAlertView.
4. Select your CustomAlertView and make all outlet connections.
Final Xib:
And you have a working alert:
PS: Adjust the UI accordingly.
In your xib class add this :
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()}
func xibSetup() {
guard let view = loadViewFromNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
Reference from this : https://medium.com/zenchef-tech-and-product/how-to-visualize-reusable-xibs-in-storyboards-using-ibdesignable-c0488c7f525d#.3c0javomy**
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
}
}
Add a subview(ShareView) but nothing happens when touching a button(coverBtn) on it if I shareVC.addsubView(self) in func showShareView ()
But If superView.addSubview(coverBtn) and superView.addSubview(sharePanel) separately, everything will be fine.
import UIKit
class ShareView: UIView {
weak var shareVC: UINavigationController?
// UI
private lazy var coverView: UIView! = {
let coverView = UIView(frame: UIScreen.mainScreen().bounds)
return coverView
}()
// transluscent cover
private lazy var coverBtn: UIButton! = {
let bounds = UIScreen.mainScreen().bounds
let frame = CGRectMake(0, 0, bounds.width, bounds.height)
let coverBtn = UIButton(frame: frame)
print ("UIScreen.mainScreen().bounds = \(UIScreen.mainScreen().bounds)")
coverBtn.alpha = 0.2
coverBtn.backgroundColor = UIColor.blackColor()
coverBtn.addTarget(self, action: #selector(ShareView.pressCoverBtn), forControlEvents: .TouchUpInside)
return coverBtn
}()
let panelHeight: CGFloat = 215
// share panel
private lazy var sharePanel: UIView! = {
// panel size
let bounds = UIScreen.mainScreen().bounds
let h = 215
let frame = CGRectMake(0, bounds.height, bounds.width, self.panelHeight)
let sharePanel: UIView = UIView(frame: frame)
sharePanel.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1.0)
// label
let labelHeight: CGFloat = 30
let labelWidth: CGFloat = 100
let labelY: CGFloat = 20
let labelFrame = CGRectMake(sharePanel.frame.width/2-labelWidth/2, labelY, labelWidth, labelHeight)
let label = UILabel(frame: labelFrame)
label.text = "分享到"
label.textAlignment = .Center
label.backgroundColor = UIColor.whiteColor()
sharePanel.addSubview(label)
// share buttons
let marginW: CGFloat = 80
let btnInv: CGFloat = 20
let btnCnt: CGFloat = 3
let btnsY = label.frame.maxY + 15
let btnA = (sharePanel.frame.width - 2*marginW - (btnCnt-1)*btnInv)/btnCnt
let wcFrame = CGRectMake(marginW, btnsY, btnA, btnA)
let pyqFrame = CGRectMake(wcFrame.maxX+btnInv, btnsY, btnA, btnA)
let wbFrame = CGRectMake(pyqFrame.maxX+btnInv, btnsY, btnA, btnA)
let wcBtn = UIButton(frame: wcFrame)
let pyqBtn = UIButton(frame: pyqFrame)
let wbBtn = UIButton(frame: wbFrame)
wcBtn.setImage(UIImage(named: "share_wx"), forState: .Normal)
pyqBtn.setImage(UIImage(named: "share_pyq"), forState: .Normal)
wbBtn.setImage(UIImage(named: "share_wb"), forState: .Normal)
sharePanel.addSubview(wcBtn)
sharePanel.addSubview(pyqBtn)
sharePanel.addSubview(wbBtn)
// cancel button
let ccW = sharePanel.frame.width/2
let ccH: CGFloat = 50
let ccFrame = CGRectMake(sharePanel.frame.width/2-ccW/2, wcBtn.frame.maxY+10, ccW, ccH)
let cancelBtn: UIButton = UIButton(frame: ccFrame)
cancelBtn.setBackgroundImage(UIImage(named: "kkk"), forState: .Normal)
cancelBtn.setTitle("取消", forState: .Normal)
cancelBtn.setTitleColor(UIColor.blackColor(), forState: .Normal)
cancelBtn.addTarget(self, action: #selector(ShareView.pressCoverBtn), forControlEvents: UIControlEvents.TouchUpInside)
sharePanel.addSubview(cancelBtn)
return sharePanel
}()
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
// override init(){
// super.init()
// }
override init(frame: CGRect) {
super.init(frame: frame)
self.addCustomView(self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addCustomView (superView: UIView) {
superView.addSubview(coverBtn)
superView.addSubview(sharePanel)
}
#objc func pressCoverBtn() {
print("press cover btn\n")
hideShareView()
}
func showShareView () {
self.addCustomView((shareVC?.view)!)
UIView.animateWithDuration(0.4, animations: {
() -> Void in
self.sharePanel.frame = CGRectMake(0, UIScreen.mainScreen().bounds.height - self.panelHeight, self.sharePanel.frame.width, self.panelHeight)
})
}
func hideShareView() {
coverBtn.removeFromSuperview()
UIView.animateWithDuration(0.4, animations: {
() -> Void in
self.sharePanel.frame = CGRectMake(0, UIScreen.mainScreen().bounds.height, self.sharePanel.frame.width, self.panelHeight)
}) { (finish)-> Void in
self.removeFromSuperview()
}
}
}
you can check the 'Debug View Hierarchy' to focus on layers so that you can see if there is a view over the button blocking it
'Debug view Hierarchy' will give you 3d view of every layer
it is situated in bottom bar
Also, you are adding both the views to the superview, try to add 'coverBtn' button to the 'sharePanel' view and then add the view to the super view, this will create button layer above the 'sharePanel' view