How to fetch the width and height of a programatically added view in Swift/ iOS? - ios

I have added a UIView and a UIImageView programatically, but I need its width and height.
Is it possible to fetch it, if yes, please share how to fetch it.
Code:
let View1: UIView = {
let viewView = UIView()
viewView.translatesAutoresizingMaskIntoConstraints = false
viewView.contentMode = .scaleAspectFit
print(UserDefaults.standard.bool(forKey: "backgroundColourSelected"))
if UserDefaults.standard.bool(forKey: "backgroundColourSelected") {
viewView.backgroundColor = self.viewColor
}else {
viewView.backgroundColor = .white
}
viewView.clipsToBounds = true
return viewView
}()
self.canvasView.addSubview(View1)
View1.centerXAnchor.constraint(equalTo: canvasView.centerXAnchor, constant: 0).isActive = true
View1.centerYAnchor.constraint(equalTo: canvasView.centerYAnchor, constant: 0).isActive = true
View1.widthAnchor.constraint(equalTo: canvasView.widthAnchor, constant: 0).isActive = true
View1.heightAnchor.constraint(equalTo: canvasView.widthAnchor, multiplier: aspectRatio).isActive = true

You need to know the life cycle of view controller.
To get height of any view use viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(view1.frame.size)
}
size property will print both final height and width.

use override func viewWillLayoutSubviews() for ViewController and override func layoutSubviews() for UIView.
Example : - for UIViewControllers
override func viewWillLayoutSubviews() {
print(yourView.frame.height)
}
for UIViews
override func layoutSubviews() {
print(yourView.frame.height)
print(yourView.frame.width)
}
Since your view is inside the function just move it out
class YourController / YourView {
private let imgView: UIImageView = {
let iv = UIImageView()
// do whatever you want with your image using iv. eg. iv.contentMode = .scaleAspectFit or iv.image = UIImage(named: "custom_img")
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imgView)
//set constraints for your
}
// then use layoutSubViews(for views) / viewWillLayoutSubViews (for controller) and calculate the height
}

Related

How to put UIView inside UIScrollView in Swift?

I am trying to add a UIView into a UIScrollView without storyboard. I made some code with the given two files(CalcTypeView.siwft and CalcTypeViewController.swift) as below. However, as shown in the screenshot image, I can see the UIScrollView(gray color) while UIView(red color) does not appear on the screen. What should I do more with these code to make UIView appear? (I've already found many example code using single UIViewController, but what I want is UIView + UIViewController form to maintain MVC pattern)
1. CalcTypeView.swift
import UIKit
final class CalcTypeView: UIView {
private let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .gray
view.showsVerticalScrollIndicator = true
return view
}()
private let contentView1: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.clipsToBounds = true
view.layer.cornerRadius = 10
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupScrollView()
setupContentView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupScrollView() {
self.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
scrollView.widthAnchor.constraint(equalTo: self.widthAnchor),
scrollView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
])
}
private func setupContentView() {
scrollView.addSubview(contentView1)
NSLayoutConstraint.activate([
contentView1.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 20),
contentView1.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -20),
contentView1.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 20),
contentView1.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -20),
])
}
}
2. CalcTypeViewController.swift
import UIKit
final class CalcTypeViewController: UIViewController {
private let calcTypeView = CalcTypeView()
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
setupView()
}
private func setupNavBar() {
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.shadowColor = .clear
navigationController?.navigationBar.standardAppearance = navigationBarAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
navigationController?.navigationBar.tintColor = Constant.ColorSetting.themeColor
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.setNeedsStatusBarAppearanceUpdate()
navigationController?.navigationBar.isTranslucent = false
navigationItem.scrollEdgeAppearance = navigationBarAppearance
navigationItem.standardAppearance = navigationBarAppearance
navigationItem.compactAppearance = navigationBarAppearance
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "bookmark.fill"), style: .plain, target: self, action: #selector(addButtonTapped))
navigationItem.rightBarButtonItem?.tintColor = Constant.ColorSetting.themeColor
navigationItem.title = Constant.MenuSetting.menuName2
self.extendedLayoutIncludesOpaqueBars = true
}
override func loadView() {
view = calcTypeView
}
private func setupView() {
view.backgroundColor = .systemBackground
}
#objc private func addButtonTapped() {
let bookmarkVC = BookmarkViewController()
navigationController?.pushViewController(bookmarkVC, animated: true)
}
}
My Screenshot
A scroll view's contentLayoutGuide defines the size of the scrollable area of the scroll view. The default size is 0,0.
In your code your contentView1 has no intrinsic size. It simply has a default size of 0,0. So your constraints are telling the scroll view to make its contentLayoutGuide to be 40,40 (based on the 20 and -20 constants) and leave the contentView1 size as 0,0.
If you setup contentView1 with specific width and height constraints then the scroll view's content size would be correct so that contentView1 would scroll within the scroll view.
A better example might be to add a UIStackView with a bunch of labels. Since the stack view will have an intrinsic size based on its content and setup, the contentLayoutGuide of the scroll view will fit around the stack view's intrinsic size.

UISegmentedControl Corner Radius Not Changing

UISegmentedControl corner radius is not changing. I also followed some answers in this question, my UISegmentedControl's corner radius still is not changing. I followed This tutorial to create UISegmentedControl.
Code:
import UIKit
class SegmentViewController: UIViewController {
private let items = ["Black", "Red", "Green"]
lazy var segmentedConrol: UISegmentedControl = {
let control = UISegmentedControl(items: items)
return control
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupViews()
}
fileprivate func setupViews(){
view.addSubview(segmentedConrol)
segmentedConrol.translatesAutoresizingMaskIntoConstraints = false //set this for Auto Layout to work!
segmentedConrol.heightAnchor.constraint(equalToConstant: 40).isActive = true
segmentedConrol.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 40).isActive = true
segmentedConrol.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -40).isActive = true
segmentedConrol.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
segmentedConrol.selectedSegmentIndex = 1
//style
segmentedConrol.layer.cornerRadius = 20
segmentedConrol.layer.borderWidth = 2
segmentedConrol.layer.borderColor = UIColor.black.cgColor
segmentedConrol.backgroundColor = .red
segmentedConrol.selectedSegmentTintColor = .darkGray
// segmentedConrol.clipsToBounds = true
segmentedConrol.layer.masksToBounds = true
}
}
(PS. Probably the answer is so simple for most people, please do not mind me, I am new in this field.)
Subclass UISegmentedControl and override layoutSubviews. Inside the method set the corner radius to what you want it to be, and you can remove the portion where you set the corner radius in setupViews():
class YourSegmentedControl: UISegmentedControl {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 20
}
}
In your view controller where you create segmentedControl create an instance of YourSegmentedControl like below.
lazy var segmentedConrol: YourSegmentedControl = {
let control = YourSegmentedControl(items: items)
return control
}()
The result is:

Autolayout aspect ratio constraints

I want to set a subview constraint as follows. If the interface is landscape (view.bounds.width > view.bounds.height),set aspect ratio of subview to be 4:3. In portrait mode, it should be 3:4. While I can always change the constraints programmatically on auto rotation, wondering if there is a clever way of designing constraints that is only one time.
You'll have to listen for the UIDevice.orientationDidChangeNotification notification like in this example:
class ViewController3: UIViewController {
let blueSubview: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var blueSubviewHeight = NSLayoutConstraint()
var blueSubviewWidth = NSLayoutConstraint()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(refreshConstraints), name: UIDevice.orientationDidChangeNotification, object: nil)
refreshConstraints()
setUI()
}
fileprivate func setUI() {
view.backgroundColor = .white
view.addSubview(blueSubview)
[blueSubview.leftAnchor.constraint(equalTo: view.leftAnchor),
blueSubview.topAnchor.constraint(equalTo: view.topAnchor),
blueSubviewWidth,
blueSubviewHeight].forEach({ $0.isActive = true })
}
#objc func refreshConstraints() {
NSLayoutConstraint.deactivate([blueSubviewWidth, blueSubviewHeight])
if view.frame.width > view.frame.height {
blueSubviewWidth = blueSubview.widthAnchor.constraint(equalToConstant: view.frame.height * (4/3))
blueSubviewHeight = blueSubview.heightAnchor.constraint(equalToConstant: view.frame.height)
}
else {
blueSubviewWidth = blueSubview.widthAnchor.constraint(equalToConstant: view.frame.width)
blueSubviewHeight = blueSubview.heightAnchor.constraint(equalToConstant: view.frame.width * (4/3))
}
NSLayoutConstraint.activate([blueSubviewWidth, blueSubviewHeight])
}
}

UIViewController Subclass does not recognize UITapGesture

With the intention to slim down my viewcontroller a little bit, i want to move the ui elements and corresponding functions into a subclass. but then my gestures don't work. how can i solve this?
MyViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SubclassMyViewController().setupUserInterface(view: view)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
SubclassMyViewController.swift
class SubclassMyViewController: MyViewController {
func setupUserInterface(view: UIView) {
// ...
view.addSubview(logoImage)
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logoImage.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
}
lazy var logoImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
// ADD GESTURE
let tap = UITapGestureRecognizer(target: self, action: #selector(doSomething))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tap)
return imageView
}()
}
Putting everything into the Viewcontroller does work. if I split it in two classes, the gesture won't be recognized.. Thanks!
I think you're confusing some things here.
SubclassMyViewController().setupUserInterface(view: view) this line creates an instance of SubclassMyViewController, which in your code sample owns the image view. Because you don't have any references to the created subclass however, it will die as soon as this line is done executing.
You could accomplish your goal by creating a static helper class instead. Here's how that would look.
class MyViewController: UIViewController {
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
MyViewControllerSetupHelper.setupUserInterface(viewController: self)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
class MyViewControllerSetupHelper {
static func setupUserInterface(viewController: MyViewController) {
let view = viewController.view!
let imageView = getLogoImageView()
viewController.imageView = imageView
let tapGesture = UITapGestureRecognizer(target: viewController, action: #selector(MyViewController.doSomething))
imageView.addGestureRecognizer(tapGesture)
view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
imageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5)
])
}
static func getLogoImageView() -> UIImageView {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
return imageView
}
}
SubclassMyViewController().setupUserInterface(view: view) is creating a new instance of subclass, but the reference of the instance is not stored.
Swift will deallocate the class if there is no reference (refer to ARC).
The lazy var logoImage is adding a GestureRecognizer which points to the doSomething() of the SubclassMyViewController-instance, which is deallocated immediately.
It would work if you do this (THIS IS NOT GOOD):
class ViewController: UIViewController {
var x: SubclassMyViewController!
override func viewDidLoad() {
super.viewDidLoad()
x = SubclassMyViewController()
x.setupUserInterface(view: view)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
You should fix your design, because:
ViewController is accessing SubclassMyViewController, which is, however, a subclass of ViewController
You doing really strange things. Gesture recognizer added to SubclassMyViewController instance. Which seams removed after creation.
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SubclassMyViewController().setupUserInterface(view: view, vc: self)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
class SubclassMyViewController: MyViewController {
func setupUserInterface(view: UIView, vc: UIViewController) {
// ...
view.addSubview(logoImage)
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logoImage.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
let tap = UITapGestureRecognizer(target: vc, action: #selector(doSomething))
logoImage.isUserInteractionEnabled = true
logoImage.addGestureRecognizer(tap)
}
lazy var logoImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
}
P.S. code is really dirty.. don't do like this
Edit
Probably this is what you wanna do:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SubclassMyViewController().setupUserInterface(view: view, vc: self)
}
#objc func doSomething() {
log.info("logo was tapped")
}
func setupUserInterface(view: UIView) {
// ...
view.addSubview(logoImage)
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logoImage.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
let tap = UITapGestureRecognizer(target: self, action: #selector(doSomething))
logoImage.isUserInteractionEnabled = true
logoImage.addGestureRecognizer(tap)
}
lazy var logoImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
}

Why does UIView fill the superView?

I am creating a subview programmatically that I would like to be positioned over a superView, but I do not want it to fill the enter superView.
I have been checking around to see if this question has been asked before but for some reason, I can only find answers to how to fill the entire view.
I would really appreciate it if someone could help critique my code and explain how to position a subView instead of filling the entire superview.
class JobViewController: UIViewController {
var subView : SubView { return self.view as! SubView }
var requested = false
let imageView: UIImageView = {
let iv = UIImageView(image: #imageLiteral(resourceName: "yo"))
iv.contentMode = .scaleAspectFill
iv.isUserInteractionEnabled = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
imageView.fillSuperview()
subView.requestAction = { [ weak self ] in
guard let strongSelf = self else { return }
strongSelf.requested = !strongSelf.requested
if strongSelf.requested {
UIView.animate(withDuration: 0.5, animations: {
strongSelf.subView.Request.setTitle("Requested", for: .normal)
strongSelf.subView.contentView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
})
} else {
UIView.animate(withDuration: 0.5, animations: {
strongSelf.subView.Request.setTitle("Requested", for: .normal)
strongSelf.subView.contentView.backgroundColor = UIColor.blue
})
}
}
}
override func loadView() {
// I know the issue lies here, but how would I go about setting the frame of the subview to just be positioned on top of the mainView?
self.view = SubView(frame: UIScreen.main.bounds)
}
}
I have my subView built in a separate file, I am not sure whether or not I would need its information since It is just what is inside of the subview.
You should add your subView as a subview of self.view and not set it equal your main view. And then set the constraints accordingly.
override func viewDidLoad() {
self.view.addSubview(subView)
subview.translatesAutoresizingMaskIntoConstraint = false
addSubview.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
addSubview.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
addSubview.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
addSubview.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
}
Regarding your initialisation problem try:
var subView = SubView()
I hope I understood your question correct.

Resources