how to DownCasting UIViewController Swift - ios

base ViewController
import UIKit
class SubViewPost: UIViewController {
#IBOutlet weak var content: UILabel!
#IBOutlet weak var recommendCount: UILabel!
#IBOutlet weak var recommendButton: UIButton!
var postInfo:PostInfo!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
child ViewController
import UIKit
class SubViewOne: SubViewPost {
#IBAction func likeWorry(_ sender: Any) {
Option.recommend(postInfo: postInfo, mRecommendCount: recommendCount, mRecommendButton: recommendButton)
}
}
and another child viewController
import UIKit
class SubViewTwo: SubViewPost {
override func viewDidLoad() {
recommendCount.alpha=0
recommendButton.alpha=0
}
}
i want add subviewOne or SubViewTwo
My ParentView
var subViewPost:SubViewPost
if postType == 1{
subViewPost = storyboard?.instantiateViewController(withIdentifier: "SubViewPost") as! SubViewOne
}else{
subViewPost = storyboard?.instantiateViewController(withIdentifier: "SubViewPost") as! SubViewTwo
}
containerView.addSubview(subViewPost.view)
raise error
Could not cast value of type
'MyApp.SubViewPost' (0x101151728) to 'MyApp.SubViewOne' (0x10114d9d0).
2018-07-10 14:40:56.007436+0900 MyApp[7207:209932]
Could not cast value of type 'MyApp.SubViewPost' (0x101151728) to 'MyApp.SubViewOne' (0x10114d9d0).
how to chagne view controller by According to postType
SubView One have Recommned
but SubView Two haven't Recommend
SubView 1,2 have same UI

The UViewController class for your scene "SubViewPost" in your storyboard is set to SubViewPost and that is what instantiateViewController will be returning. You cannot downcast an instance of SubViewPost to SubViewOne or SubViewTwo.
You could define two identical scenes in your storyboard, each with the appropriate view controller class, but that would require a lot of duplication.
Since the only difference is the visibility of the recommendButton and recommendCount elements, why not just handle that via a property:
var subViewPost = storyboard?.instantiateViewController(withIdentifier: "SubViewPost") as! SubViewPost
subViewPost.recommendVisible = (postType == 1)
SubViewPost.swift
var recommendVisible = true
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
recommendCount.isHidden = !recommendVisible
recommendButton.isHidden = !recommendVisible
}

The error message is clear. When you say
storyboard?.instantiateViewController(withIdentifier: "SubViewPost")
what you get from the storyboard is a view controller whose class is SubViewPost. You cannot wave a magic casting wand and claim that it is a SubViewOne instead; it isn't.
If you wanted this view controller to be a SubViewOne, then you should have declared it as a SubViewOne in the storyboard in the Identity inspector.
I think I see what you are trying to do, and why you are confused about why you can't do it this way.
What's in the storyboard is an instance, not a class. Yes, it is an instance of some class, but it is an instance of that class. So when you design the interface in the storyboard, you are designing the interface associated with that one instance of that one class.
If your goal is to have a single interface associated with multiple classes, the interface must be generated in code or loaded from a View .xib file — not designed in a storyboard.
However, you would be better off not trying to use subclassing in this situation in the first place. What I do in a similar situation is give my view controller an enum property that says which "kind" of view controller it is, and obey accordingly in code. That a way, a single class serves multiple purposes.

Related

Load segmented views in ViewController but UISegmentControl is in UIView class

I have created a UIView class (SegmentControl.swift) and added the UISegmentControl using an xib. I have another viewcontroller where I refer this UIView class to add the segment control.
My question here is since I have the UISegmentControl in another UIView class, how will I update the index and load the respective containerViews in the ViewController?
#IBAction func didChangeIndex(_ sender: UISegmentedControl) { } will work only if I have Segment control in ViewController.
Please provide your suggestions on how can I load the containerviews when the UIControlSegment is in another class.
This is the code I have:
SegmentControl.swift
class SegmentControl: UIView {
#IBOutlet weak var segmentView: UISegmentedControl!
// Loading nibs are there.. just that I didn't include here
func create(titles: [String]) {
items.enumerated().forEach { (index, item) in
segmentView.setTitle(item, forSegmentAt: index)
}
}
}
ViewController.swift
func showSegmentControl() {
let segmentedView = GenericSegmentedView.create(items: ["A", "B"])
stackView.addArrangedSubView(segmentedView)
}
Two container views --> aInfoView and bInfoView are intialised in View Controller.
How will I load them on switching the segments since they are in UIView class. I couldnt find any answers here. Please help!
Thank you!!
You can add a target to the segmented control even if it's contained within another view. The segmented control is an accessible property on your SegmentControl class. From the SegmentControl's parent view controller add the target action to the your SegmentControl's child segmentView.
func showSegmentControl() {
let segmentedView = GenericSegmentedView.create(items: ["A", "B"])
stackView.addArrangedSubView(segmentedView)
segmentedView.segmentView.addTarget(self, action: #selector(segmentControlChanged(_:)), for: .valueChanged)
}
// Function will be called when value changed on the SegmentControl's segmentView
#objc func segmentControlChanged(_ sender: UISegmentedControl) {
print(sender.selectedSegmentIndex)
}
Something as aside, interchanging segmentControl and segmentView can be confusing to people not familiar with your code. Maybe a more descriptive name for the SegmentControl like SegmentControlContainer or something would help make the distinction.

Setting delegate for CustomUiView getting EXC_BAD_ACCESS

I have created a custom UIView and have a protocol set for it. Now from the View Controller when I set the delegate to self I am getting an EXC_BAD_ACCESS.
---The View Controller Code------
class VerificationController: UIViewController, LoadingViewDelegate {
#IBOutlet weak var instructionView: LoadingView!
override func viewDidLoad() {
super.viewDidLoad()
instructionView.randomTextIndexes = [1]
instructionView.delegate = self
}
...
}
// "instructionView" is the UIView outlet and "LoadingView" is the class
--This is the custom View Code ------
protocol LoadingViewDelegate {
func generated(random code:String)
}
class LoadingView: UIView {
var delegate:LoadingViewDelegate?
var randomTextIndexes:[Int] = []
}
I am getting EXC_BAD_ACCESS when I try to access the delegate as well as the randomTextIndexes from the viewDidLoad() method of the view controller. Could you please tell me what I am missing here.
My best guess is that you forgot to set the class of the custom view in the Interface Builder. Check the Identity Inspector for your object in the IB, it should look like this:
If it isn't set, then that's the problem.

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.

Segue from UITableView to UITabBarController

I am creating an app that allows the user to see a random quote everyday. In this app, the user is asked 3 questions before being able to actually use the app. The last question is a simple "What is your favorite category/topic". With this prompt, the user will tap a cell and be brought to a Tab Bar Controller with the first "Child" view controller being the quote itself.
Problem:
I want the user to be able to tap a UITableViewCell and the one they tap effects which TabBarController they are brought to.
That is the photo with the errors I am running into so far. Here is the code.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "bookSegue")
{
let bookQuoteTabBar = segue.destinationViewController as! UITabBarController
let bookQuoteScreen = bookQuoteTabBar.viewControllers?[0] as? bookQuoteScreen
}
else if(segue.identifier == "businessSegue") {
let businessQuoteTabBar: UITabBarController = segue.destinationViewController as! UITabBarController
let businessQuoteScreen = businessQuoteTabBar.viewControllers?[0] as? businessQuoteScreen
}
}
Eventually, there will be more topics, meaning more segues. But for now, I'm starting with two
The segues for each TabBarController are:
"bookSegue"
"businessSegue"
The Tab Bars are:
"bookQuoteTabBar" and "businessQuoteTabBar"
The First "Child" View controllers are:
"bookQuoteScreen"
"businessQuoteScreen"
Should I have written something else? Did I correctly name the Segues, identities, and classes of each object? If you need more information or references, comment what I should add and I will add it within minutes. Thank you in advance!
---------Recent edits---------
BooksQuoteScreen:
import Foundation
import UIKit
class BooksQuoteScreen: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
BusinessQuoteScreen:
import Foundation
import UIKit
import Social
class BusinessQuoteScreen: UIViewController {
//============================//
//********** Outlets *********//
//============================//
let utility = Utility()
#IBOutlet weak var quoteDisplay: UILabel!
#IBOutlet weak var authorDisplay: UILabel!
#IBOutlet weak var quoteBackground: UIImageView!
...
}
The errors in your screenshot ("Use of undeclared type ....") indicate that Xcode does not recognise bookQuoteScreen and businessQuoteScreen as valid types. In the highlighted lines, eg.
let bookQuoteScreen = bookQuoteTabBar.viewControllers?[0] as? bookQuoteScreen
the type (specified after "as? ") must match up with the class name defined in your .swift files. Check very carefully that the names used match the class names (presumably) defined in "BusinessQuoteScreen.swift" and "BooksQuoteScreen.swift". Without seeing the contents of those files, I can't be certain, but I suspect the leading character needs to be upper case (it should be for class names), and you might need an "s" in
"BooksQuoteScreen":
let bookQuoteScreen = bookQuoteTabBar.viewControllers?[0] as? BooksQuoteScreen
and
let businessQuoteScreen = businessQuoteTabBar.viewControllers?[0] as? BusinessQuoteScreen

Passing data between views in ONE ViewController in Swift

All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.

Resources