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....
}
Related
I have a UIViewController that implements a custom UIView, so;
override func loadView() {
view = CustomView()
}
The custom view has a few lables and buttons and all the normal stuff, problem is in my viewController I have a request, and when that request is done, I'd like to update some of those lables/buttons.
Right now, in my CustomView, I have functions, such as;
func updateView() {
labelOne.isHidden = true
LabelTwo.isHidden = false
}
So I call the appropriate function from my viewController when the request is done.
This works, but it feels wrong, is there a neater way to update the subviews of my custom UIView, from my viewController? Should I maybe be using protocols or delegates?
One thing I've found quite neat in the past is passing the model directly to the custom view, then using didSet to trigger updates.
class CustomView: UIView {
let labelOne = UILabel()
let labelTwo = UILabel()
var object:CustomObject! {
didSet {
self.labelOne.text = object.name
self.labelTwo.text = object.description
}
}
...
}
This means in your UIViewController you can do the request and then pass the model straight to the custom view.
RequestHelper.getObject() { object in
self.customView.object = object
}
Obviously here I'm guessing at your request and object names but hopefully you get the idea.
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 can I create the types of view that slide up from the bottom of the screen as pictured below? Is this a built-in view type or something custom?
The screen shots are from Wunderlist and The Hit List iOS apps respectively.
Just had a look at The Hit List there. Seems like they're just animating a view up from the bottom.
I'd go about this by creating a UIView, let's call it slidingView. You can do it in your existing storyboard or create a new .xib file for it. Then, when you call your viewDidLoad for the view controller that this slidingView will be contained in, move the slidingView view off screen and animate it in whenever you want.
Example:
import UIKit
class ViewController: UIViewController {
private struct Constants {
static let animationDuration: TimeInterval = 0.3
static let marginFromTop: CGFloat = 32.0
}
#IBOutlet private weak var slidingView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
moveSlidingViewOffScreen()
}
// MARK: - Actions
#IBAction func buttonTap(_ sender: UIButton) {
UIView.animate(withDuration: Constants.animationDuration) {
self.slidingView.frame.origin.y = Constants.marginFromTop
}
}
// MARK: - Private functions
private func setupSlidingViewOffScreen() {
slidingView.frame.origin.y = view.frame.height
}
}
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.
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.