Programmatically assigning class to a UIView created in Storyboard (Swift) - ios

Question: How do I programmatically assign a class to a UIView created in Storyboard?
I have a UIView created in IB (graphView) in which I would like to display one of several graphs, their definitions created in their respective classes.
To do this, I created another view programmatically then assigned the class of the selected graph to that view, then added it to the original view:
#IBOutlet weak var graphView: UIView!
func displayGraph(choice: Int) {
var selectedGraphView = UIView()
switch choice {
case 1: selectedGraphView = class1()
case 2: selectedGraphView = class2()
case 3: selectedGraphView = class3()
default: selectedGraphView = class1()
}
selectedGraphView.frame = CGRect(x: 0, y: 0, width: graphView, height: graphView)
graphView.addSubview(selectedGraphView)
}
Things seem to work, but do I really need that second UIView (selectedGraphView)?
I tried changing it to:
func displayGraph2(choice: Int) {
switch choice {
case 1: graphView = class1()
case 2: graphView = class2()
case 3: graphView = class3()
default: graphView = class1()
}
}
but I don't know how to get the contents of the class to run.
Can I do it this way? If so, what is missing?
EDIT: Actually, there are 3 UIViews: the one in IB, the one to hold the graph, and the class itself creates one.
If, say class1 has nothing but print("entered class1"), in the first case (displayGraph), I see "entered class1" printed. In the second (displayGraph2), I get nothing.
EDIT2:
I can assign any of the classes to graphView in IB and get the print message from the class. How do I assign a class to graphView programmatically (since what I attempted in displayGraph2 doesn't work)?

When you add a UIView in Storyboard, you can assign its Custom Class. However, at run-time, you cannot change its class this way:
#IBOutlet var graphViewHolder: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// cannot do this
let v = View2Class()
graphViewHolder = v
}
One common approach, as you've done, is to add a UIView in your Storyboard as a "holder" (or "container") view. Then, in code, instantiate an instance of your desired Custom Class and add it as a subview to the "holder" view.
However, if you do this:
selectedGraphView.frame = CGRect(x: 0, y: 0, width: graphView, height: graphView)
graphView.addSubview(selectedGraphView)
The newly added subview will have the size of the "holder" view, and you'll run into layout issues if / when the holder view changes size (such as on device rotation).
So, you can do it like this:
// let's name it "graphViewHolder" so we know we're using it as a "holder" view
#IBOutlet var graphViewHolder: UIView!
var currentChoice: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
displayGraph(choice: 1)
}
func displayGraph(choice: Int) {
var selectedGraphView: UIView?
switch choice {
case 1: selectedGraphView = View1Class()
case 2: selectedGraphView = View2Class()
case 3: selectedGraphView = View3Class()
default: selectedGraphView = View1Class()
}
// unwrap optional
guard let sgv = selectedGraphView else { return }
// does the "holder" view already have any subviews?
// if so, remove them
graphViewHolder.subviews.forEach { v in
v.removeFromSuperview()
}
// add the new view as a subview of the "holder" view
graphViewHolder.addSubview(sgv)
// we need to give the new view auto-layout properties
sgv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain it to all 4 sides of the "holder" view
sgv.topAnchor.constraint(equalTo: graphViewHolder.topAnchor),
sgv.leadingAnchor.constraint(equalTo: graphViewHolder.leadingAnchor),
sgv.trailingAnchor.constraint(equalTo: graphViewHolder.trailingAnchor),
sgv.bottomAnchor.constraint(equalTo: graphViewHolder.bottomAnchor),
])
}
Now the subview will follow the "holder" view's constraints.
Note that if you want to do something to the loaded view, you'll have to make sure it's the right class.
So, for example, if we want to call a custom func in View2Class:
// make sure the "holder" view has a subview
guard let v = graphViewHolder.subviews.first else {
print("Graph View Holder has no subviews!")
return
}
// make sure the subview is a View2Class instance
guard let v2 = v as? View2Class else {
print("The Holder subview is not View2Class!")
return
}
// call a func in View2Class
v2.myFunc()

Related

Programmatically get the custom UIViews embedded inside UIStackView

In my ViewController, I have 12 custom UIViews(named CardView).
I am trying to iterate through the subviews of the ViewController programmatically to find the custom view(CardView) and do some configuration. Following is my code to count the number of CardViews.
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews{
if subview is CardView{
count = count + 1
}
}
return count
}
However, it is returning me '0' and the reason being my views are embedded inside UIStackViews. I have 3 horizontally aligned stack views inside a vertically aligned one like-
How can I get my CardViews programmatically. Any help would be appreciated.
You can do a few flat maps to flatten your view structure first, then count them:
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews.flatMap { $0.subviews }.flatMap { $0.subviews }{
if subview is CardView{
count = count + 1
}
}
return count
}
But I feel like you are doing things the wrong way around. The number of cards sounds like something in your model. You should have a variable called cardCount and the cards on the screen are supposed to change according to that variable, not the other way around.
You create IBOutlets to each of the horizontal stack views. Then loop through the subviews in the stackviews.
#IBOutlet weak var StackView1: UIStackView!
#IBOutlet weak var StackView2: UIStackView!
#IBOutlet weak var StackView3: UIStackView!
for customView in StackView1.arrangedSubviews
{
// Do task
}
Try this
private func cardCount() -> Int
{
var count = 0
for subview in self.view.subviews
{
if let stackView: UIStackView = subview as? UIStackView
{
let arrangedViews = stackView.arrangedSubviews
for cardView in arrangedViews
{
if cardView is CardView
{
count = count + 1
}
}
}
}
return count
}
From the pictorial diagram you shared, it seems the StackView is the first child. Whenever you get the subviews, only the first child views are returned. So self.view.subviews would result in only one UIStackView. Give a tag to each stack view. Then, in code :
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews{
if subview.tag == 10 { // parent stack view
for subviewStack in subview { // Get subviews of parent stackview
if subviewStack.tag == 11 { // First stack view child
for subViewSubStack in subviewStack.subviews { // Get card views in this stack view
// Apply your count logic
}
}
}
}
}
return count
}
I have written only the first condition. You might add others.
Having said this, I won't say this is the most optimum solution. This can and should be refactored.
Create an extension method like this. It will count all CardView instances in the view controller.
extension UIView {
func cardCount() -> Int {
switch self {
case let self as CardView:
return 1
case let self as UIStackView:
return self.arrangedSubviews.reduce(0, { $0 + $1.cardCount() })
default:
return self.subviews.reduce(0, { $0 + $1.cardCount() })
}
}
}
and call this method in viewDidLoad
class ViewControllerd: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(self.view.cardCount())
}
}

How to put collection view and page view within one Scene

I am creating a screen like below:
But the problem is, pagedView requires my view controller to inherit from UIPagedViewController, while colleciton view requires inheriting from UICollectionViewController.
Is there a way to achieve this?
You don't have to use UIPagedViewController or UICollectionViewController make it inherit from UIViewController , and inside say loadView/ViewDidLoad use
let pager = UIPagedViewController()
// then add it as a child vc and constraint it's view or set a frame
and
let collec = UICollectionView(///
// add to view and constraint also
The above should be instance vars also , so their delegates/dataSources being retained
what about like this?
class MyPagedView: UIView, UIPageViewControllerDataSource {
// add your pages
}
class MyCollectionView: UICollectionView, UICollectionViewDataSource {
// add your collection view
}
class ViewController: UIViewController {
lazy var myPages: MyPagedView = {
let pages = MyPagedView()
return pages
}()
lazy var myCV: MyCollectionView = {
let cv = MyCollectionView()
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myPages)
view.addSubview(myCV)
}
}
Of Course, you will have to set your constraints according to your need.

Access TextField from another class or method without storyboard

Okay, this might be one of the most basic questions ever, but all answers I find use storyboard to declare an outlet for a label, textfield or whatever element that needs to be changed. I, however, don't use storyboards and write everything in code. Now I have a function setupViews, where I define a textfield:
let usernameInput = UITextField()
Now, I can perfectly set the text or placeholder or whatever inside this setupViews() class, but how can I access it outside? For example, if I have a function logIn(), I want to call usernameInput.text and use it in this function.
Someone who can point me in the right direction? Do I need to declare this textfield globally, in another file, or something else?
When I create my views in code I always associate a property with the view that has all those various display values.
I have not tested this code to see but hopefully the following will give you an idea.
import UIKit
struct {
var name: String
}
class CustomViewController : UIViewController {
// some struct which contains data for view
var customViewData : ViewDataInfo? {
didSet {
labelOnScreen.text = customViewData.name
}
}
var labelOnScreen: UILabel = {
let label = UILabel()
label.text = "Placeholder information..."
// stuff auto layout
label.translateAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.addSubview(label)
// set your constraints here
}
}

How to make UI elements in class out of View Controller

I'm making app and I'm trying to make a View which contains a Label with a question. I want this view in my app and because I will use it repeatedly, I made a class (If I want to make some change, I can do It from one place). The UIView is called questionView (var questionView = UIView()). Problem is when I want to make questionView a subview of view. The error says that I don't have have "view" which I understand. I don't have view but how can I get it? Thank you
This is what is inside my Question class:
import Foundation
import UIKit
class Question {
// PROPERTIES:
var questionLabel = UILabel()
var questionView = UIView()
// METHODS:
func createQuestion (input:String) {
// some code .... not important
// THIS:
self.view.addSubview(questionView)
}
// ... next code, also not important
}
UPDATE:
There is my solution. It works BUT I think that it's not correct from a programming standpoint. Can anybody tell me anything about it? Thank you
My class in separate swift file:
My class in separate swift file:
class LabelClass {
var view = UIView()
init (view: UIView) {
self.view = view
}
var lbl = UILabel()
var lblView = UIView()
func makeLabel () {
self.lbl.frame = CGRectMake(0, 0, 150, 50)
self.lbl.text = "Text text text"
self.lbl.numberOfLines = 0
self.lblView.frame = CGRectMake(20, 20, 150, 50)
self.lblView.addSubview(self.lbl)
self.view.addSubview(lblView)
}
}
Piece of code my ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
// Added code:
var object = LabelClass(view: self.view)
object.makeLabel()
}
I don't know Swift, but as far as I know, only instances of UIViewController have a view property, the class Question does not, so you cannot add subviews to it.
What you probably want is making a subclass of UIView which contains a question label, or to add the questionLabel as a subview of questionView.
It is because you are trying to add your view to a normal Swift class which doesn't have a self.view instance. Your Question class must be a subclass of UIViewController cocoa class that it has a self.view instance and override methods.
class Question:UIViewController {
// PROPHERITIES:
var questionLabel = UILabel()
var questionView = UIView()
// METHODS:
override func viewDidLoad() {
createQuestion("foo")
}
func createQuestion (input:String) {
// some code .... not important
// THIS:
self.view.addSubview(questionView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// ... next code, also not important
}

array of multiple UIViews without stacking

it seems normally if you have multiple UIViews, you stack them by using insertSubview at index.
self.view.insertSubview(testUIView, atIndex: 1)
but i want to use a single UIView.
i have an array of different UIViews
let plotUIViews: [UIView] = [testView(), FaceView()]
here is testView
import UIKit
class testView: UIView {
override func drawRect(rect: CGRect) {
var greenView = UIView(frame: CGRectMake(100, 200, 100, 100))
greenView.backgroundColor=UIColor.greenColor()
}
}
i have a generic UIView
#IBOutlet var testUIView: UIView!
and would like to do this, it is a bit perl-ish rather than swift-ish (to save memory of having all views drawn on each layer then manipulating the index)
self.testUIView?.removeFromSuperview()
self.testUIView = nil
switch plotValue{
case 0:
self.testUIview=plotUIViews[0]
case 1:
self.testUIview=plotUIViews[1]
case 2:
self.testUIview=plotUIViews[2]
etc
}
self.view.insertSubview(testUIView, atIndex: 2)
nothing shows in testUIView
thanks for any help or pointers.
It sounds like what you want is to only display one of your plot views at a time. So, remove the prior plot view, if there is one, before showing the new one:
self.testUIView?.removeFromSuperview()
self.testUIView = nil
switch plotValue {
...
}
self.view.insertSubview(testUIView, atIndex: 2)

Resources