I have the following problem, I am building a school schedule and I have 2 viewControllers with scrollView in each. In the viewController "Bloques" I have a containerView at the side of the list (The list is inside a scrollView), which will contain the "Dias" viewController, the idea is that when I switch from one day to another using paging in "Dias", don't move the list of bloques. The problem is that the list is too long, so I use the scrollView in both viewController, which have the same number of frames, then when I scroll on the Y axis for example "Bloques", I want it to also perform Scroll in the "Dias" scrollView. In another project I did something similar, but within the same viewController, which did not cause me major complications, but being in different viewControllers generates the following error:
Fatal error: unexpectedly found nil while unwrapping an Optional value:
Error:
Structure of viewControllers:
BLOQUES
#IBOutlet weak var bloquesScrollView: UIScrollView!
override public func viewDidLoad() {
super.viewDidLoad()
bloquesScrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.restorationIdentifier == "bloques" {
let ejey = bloquesScrollView.contentOffset.y
let pruebaController: prueba = prueba()
pruebaController.mover(y: Int(ejey))
}
}
DIAS
#IBOutlet weak var diasScrollView: UIScrollView!
func mover(y: Int?) {
print("casi \(y)")
diasScrollView.setContentOffset(CGPoint(x: 0, y: y!), animated: false)
}
override func viewDidLoad() {
super.viewDidLoad()
diasScrollView.delegate = self
}
Correcting the question, I look for a method in which different UIViewController (Monday, Tuestaday, etc) which contain ScrollView corresponding to blocks in the UIViewController "Bloques", give the movement signal to the ScrollView of "Blocks" if they too move or vice versa.
It should be mentioned that Monday, Tuesday, etc. Are contained in a Container View in "Bloques".
I understand that Container View controller manages a view just like any other UIViewController subclass, in this case, the image in the middle is the UIViewController of the Container View, which will run Monday, Tuesday, etc. Previously what I realized was a list of "Bloques" was inside each UIViewController (Monday, Tuesday, etc.), so it was easier to do it, but the view is strange, since the listing should be static and not be move and appear again on the next page (From Monday to Tuesday for example). I do not know if there is an easier method than the one I am proposing.
New Situation
EDIT
The problem is that in your first code let pruebaController: prueba = prueba() creates a whole new prueba instance. That is not the same prueba that has a scrollView, so its scrollView is nil and you crash when you try to use it.
Please try this code..
BLOQUES
#IBOutlet weak var bloquesScrollView: UIScrollView!
override public func viewDidLoad() {
super.viewDidLoad()
bloquesScrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.restorationIdentifier == "bloques" {
let ejey = bloquesScrollView.contentOffset.y
// try this code....
let vc = UIStoryboard(name: '<Here Your storyboard name (default : "main">', bundle: nil)
.instantiateViewController(withIdentifier: '<Here Your DIAS Identifier>') as! prueba
vc.mover(y: Int(ejey))
}
}
Related
I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.
I hava a UIPageViewController that holds an array of ContentUIViewController, these have scrollviews in them.
When the scrollview scrolls I want to recognise this in the view controller that is the parent of the UIPageViewController (added as child)
What would be the best route to achieve this? as currently I tried delegating the didScroll back to the viewmodel for the ContentUIViewController then feeding that into the parent of the UIPageViewController to adjust header height that I want to collapse, but it doesn't work well / is very hacky
Is there a way to read delegate output in the a child vc to the parent vc without feeding it back through viewmodels? This is proving tricky due to the array nature of the UIPageViewController rather than a single child VC.
Updated:
private func generateContentControllers() -> [UIViewController] {
var viewControllers: [UIViewController] = []
viewControllers.append(ScrollableContentViewController(contentViews: infoViews))
viewControllers.append(reviewsVC)
viewControllers.append(ScrollableContentViewController(contentViews: helpViews))
}
return viewControllers
}
Provided via
var scrollingViewControllers: [UIViewController] { get }
Added with
if let firstViewController = self.viewModel.scrollingViewControllers.first {
pagingController.setViewControllers([firstViewController], direction: .forward, animated: false)
}
Here is how you can get that working.
1. Create a handler in ContentUIViewController that will be called in scrollViewDidScroll(_:) method for each contentOffset change.
class ContentUIViewController: UIViewController, UIScrollViewDelegate {
var handler: ((CGPoint)->())?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
handler?(scrollView.contentOffset)
}
}
2. In ParentVC, create an instance of UIPageViewController and add it as a child to it. I think you did that already.
What you need to do next is create an array of ContentUIViewController and for each instance set the handler that we created earlier.
This handler will be called every time the scrollView is scrolled in that particular ContentUIViewController. We'll get the contentOffset of the scrollView here. Call updateHeaderHeight(for:) with the obtained contentOffset to adjust the header height.
class ViewModel {
private func generateContentControllers() -> [ContentUIViewController] {
var viewControllers: [ContentUIViewController] = []
//add your code here....
return viewControllers
}
lazy var scrollingViewControllers: [ContentUIViewController] = {
return self.generateContentControllers()
}()
}
class ParentVC: UIViewController {
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.scrollingViewControllers.forEach {
$0.handler = {(offset) in
self.updateHeaderHeight(for: offset)
}
}
}
func updateHeaderHeight(for offset: CGPoint) {
//add the code to adjust header height here...
}
//add rest of the code for pageViewController here....
}
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.
I want my several view controllers to have a player in the bottom. This player consists of 2 views: the player and a button which toggles it (can be hidden or expanded).
Now I use the code below in each view controller to add this player.
#IBOutlet weak var broadcastView: BroadcastView!
#IBOutlet weak var broadcastViewBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var avatarImageView: UIImageView!
#IBAction func toggleBroadcastMode(_ sender: ToggleBroadcastButton) {
if sender.isExpanded {
broadcastViewBottomConstraint.hideBroadcastView()
} else {
broadcastViewBottomConstraint.expandBroadcastView()
}
animateBroadcastToggle()
sender.toggle()
broadcastView.toggleBroadcastView()
}
Is there a way not to duplicate the code over and over? Maybe I can create parent VC or View to do it? If so, then how?
I personally would subclass a UINavigationController and have it in there, that way you can navigate through the flow while the player stays looking good at the bottom, if you need a VC to interact with it then you can
if let nav = navigationController as? MyPlayerNavController {
nav.PlayThis()
}
you can have it change size and everything from there and you wont lose it during transitions and stuff like the music app when playing music.
Add it as a subview of the keyWindow. That way it will always stay in all the viewControllers.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var overlayViewFrame = UIScreen.main.bounds
overlayViewFrame.origin.y = overlayViewFrame.height - 200
overlayViewFrame.size.height = 200
let overlayView = UIView(frame:overlayViewFrame)
overlayView.backgroundColor = UIColor.cyan
UIApplication.shared.keyWindow?.addSubview(overlayView)
}
This code is a sample generic code. If you want certain VCs to not show this, keep this overlay view in a singleton, and hide in appropriate VCs.
Output screenshots:
I have created a UIViewController. Within this view controller I have a container view.
In the code I instantiate the container view like so:
var answerButtons: AnswerButtonsViewController!
override func viewDidLoad() {
super.viewDidLoad()
answerButtons = AnswerButtonsViewController()
answerButtons.delegate = self
println(answerButtons.name) // This prints out the correct string
println(answerButtons.answerOne) // This prints out nil
println(answerButtons.buttons) // This prints out an empty array
retrieveSelectedCategory(selectedCategory!)
}
However the container view is definitely instantiate because within the container view class I set the background color of the UIButton's and this displays correctly within my app.
Here is the code from my container view:
#IBOutlet var answerOne: UIButton!
#IBOutlet var answerTwo: UIButton!
#IBOutlet var answerThree: UIButton!
#IBOutlet var answerFour: UIButton!
#IBOutlet var answerFive: UIButton!
#IBOutlet var answerSix: UIButton!
#IBOutlet var answerSeven: UIButton!
#IBOutlet var answerEight: UIButton!
var buttons: Array<UIButton> = []
var name = "Is this set"
var delegate: AnswerButtonsViewControllerDelegate?
override func viewWillAppear(animated: Bool) {
addAnswerButtonsToArray()
buttons.map(customiseButtons)
}
func addAnswerButtonsToArray() {
buttons.append(answerOne)
buttons.append(answerTwo)
buttons.append(answerThree)
buttons.append(answerFour)
buttons.append(answerFive)
buttons.append(answerSix)
buttons.append(answerSeven)
buttons.append(answerEight)
}
func customiseButtons(button: UIButton) {
button.layer.cornerRadius = 5
button.setTitleColor(UIColor.blackColor(), forState: .Normal)
button.backgroundColor = GSNColor.veryLightGrayColor()
var title: String! = button.titleLabel?.text
title.replaceRange(title.startIndex...title.startIndex, with: String(title[title.startIndex]).capitalizedString)
button.titleLabel?.text = title
}
#IBAction func answerButtonPressed(sender: UIButton) {
delegate?.answerButtonPressed(sender)
}
Also the IBAction of the buttons work.
So I can't understand why my property answerButtons is initiated and the name property is set ok. However the properties are nil when printed and the array empty. Yet the view is being displayed correctly.
Any insights would be much appreciated and if you need any more details please let me know.
Edit
Here is a picture of storyboard with the connections:
The controller embedded in the container view is instantiated at the same time that the controller with the container view is. You should not instantiate it yourself. You only need to get a reference to it. A controller in a container view is a child view controller of the controller whose subview is the container view. Instead of this,
answerButtons = AnswerButtonsViewController() // this creates another instance that you never use, and is never on screen
You should have this,
answerButtons = self.childViewControllers[0] as AnswerButtonsViewController
Here, you are just instantiating the view controller.You only get a reference to it.You have to instantiate the view controller using storyboard
let SB: UIStoryboard = UIStoryboard(name: StoryboardName, bundle: Bundle.main)
let VC = SB.instantiateViewController(withIdentifier: StoryboardIdOfViewController)
if let _ = VC.view {
//Do here
}
The IBOutlet's will be nil until the view is loaded. All you are doing is instantiating the container view controller, not displaying or loading the view.
The IBOutlet's won't be available until the AnswerViewController's loadView and viewDidLoad method's are called.
You can check:
if (answerButtons.isViewLoaded()) {
}
To see if the view is loaded. If you want the view to be loaded you'll either need to access the view property of the controller, or add the view to the view hierarchy as a subview.