Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best approach for composing UIViews to create complex views? [closed]

Transitioning from the web-stack to iOS wonderland, I am finding it hard to understand how to re-structure my thoughts for MVC and effectively structure my UI on iOS.

Premise:

  • In the web-stack:
    • Controller generally corresponds to a URL and view represents the page.
    • Views have ways to not-repeat code with CSS +Javascript + fragments.
  • In iOS:
    • Once on a new screen which correspond to a UIViewController.
    • We have a UIView which composes its own UIViews
    • UIViews exists with UIViews, UIViewController with UIViews, UIViewController with inline UIViews
    • We definitely delegate heavy-lifting or data logic to nearest controller
    • It feels mostly View first in the iOS which is great

Question:

What is the best way to think about composing views to my self.view with:

  • A view as UIView class
  • A view occurring as UIViewController with an associated UIView class.
  • A view occurring as UIViewController with inline UIView

Example view hierarchy:

------------------------------
|             A              |
|                            |
|    --------------------    |
|    |        B         |    |
|    |                  |    |
|    |  --------------  |    |
|    |  |     C      |  |    |
|    |  |            |  |    |
|    |  |            |  |    |
|    |  |            |  |    |
|    |  |            |  |    |
|    |  |            |  |    |
|    |  |            |  |    |
|    |  --------------  |    |
|    |                  |    |
|    --------------------    |
|                            |
------------------------------

[Details to be Specific] My ~uncomfortable~ experiences:

  • A might [self addView:B_view] (If B was just UIView)
  • A might also [self addView:B_viewController.view] (If B had a view controller)
  • A might also [B setChildView:C_view]
  • We can have B_viewController.delegate = A & C_viewController.delegate = A
  • We can have B_viewController.delegate = A & C_view.delegate = B
  • After all these cases co-existing, we have to think about the bounds
  • I see C's concerns answered by A via long delegate chains.

The logic is scattered across delegates, viewControllers and views.

Separating Concerns - But How ?

Apart from the argument "abstract well and separation of concerns" that can improve above experiences. I believe its the bleeding of viewController and viewController.view being composed to easily cause extensive coupling.

This quickly becomes confusing and feels like spaghetti and hard to control despite efforts.

That said,

  • What are the best practices to address above pitfalls ?
  • What are the absolute don'ts ?
  • How do we avoid long delegate chains when its hard to come up with good arguments since when having delegate its effectively saying you can handle this better ?
like image 996
Yugal Jindle Avatar asked Nov 05 '16 00:11

Yugal Jindle


3 Answers

Perhaps, what you are dealing is with the MVC - Massive View Controllers dilemma. Here is an interesting read where Marcus speaks of managing code effectively by putting the code where it belongs, networking layer, persistence layer or the presentation layer(ideally the viewcontroller).

http://www.cimgf.com/2015/09/21/massive-view-controllers/

Apart from the delegation pattern we also have block/closure based approach that might help to clean up the code.

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html

In general, Robert C. Martin's clean code approach could be of help here. The way to implement it through something called as VIPER architecture. Here is a tutorial on that.

https://www.objc.io/issues/13-architecture/viper/

Objective C, as a language embraces the delegation pattern and it is a standard practice to use it for most of one's concerns.

Welcome to the iOS platform.

like image 131
Arun Balakrishnan Avatar answered Nov 16 '22 06:11

Arun Balakrishnan


I failed at asking similarly broad questions at stackoverflow - seems like community favors more specific technical question. But I'll give it a shot. Keep in mind that all following are my opinions and not hard and fast rules.

I feel like it is a composite question about message passing and design patterns. In terms of components communication (whether they are views or controller) there are many ways to go about it, each has it's + and - (or rather use cases). There is: delegation, responder chain, blocks, notifications, reactive signal - just to name a few.

TL;DR

  • My personal preference is definitely to keep the view as simple as possible and put the business logic someplace else (depending on the pattern you chose) allowing for better composition.
  • Your gut feeling aligns with mine: whenever your component (view or view controller) is doing self.superview or self.parentViewController (some say even self.navigationController) it probably violates encapsulation and there is a better way.
  • Long delegate chains is definitely an issue. Sometimes they are easy to follow, sometimes not, but it's not the worst thing in the world, although chances are there is a better way to handle communication.

Following are few random observations that might lead you somewhere (or might not).

MVC is a quick (and dirty) way to get people in iOS

In my opinion, MVC is a great pattern for small project (it is easy from "easy vs simple"). But from personal experience it quickly gets out of hands when the complexity of the screen you're working on grows. Tendency is for the controller to handle everything from user interaction to networking and it becomes a 1k-long monster really quick.

Sadly a lot in iOS architecture is relying on UIViewController. Often it inevitably becomes a first point of contact of your code with OS, but it doesn't have to handle everything internally. Instead it can route responsibilities to appropriate components, i.e.

func loginButtonDidTap(_ sender: UIButton) {
  loginManager.beginLogin(from: self)
  analytics.reportLogin(with: sender)
  // ...
}

It is okay to compose controllers, too

I didn't quite notice from your description, but if you prefer to go with MVC it is a completely valid option to compose controllers. It is a very common situation when building for iPad but even on iPhone every screen can easily include completely independent pieces where every one of them is a separate ViewController, then you should look into what's called "UIViewController containment". Mostly it allows for better separation and reuse, but again, doesn't solve the main issue.

Definitely explore other patterns

If you are reading this, you are very eager to learn🙂 In this case, I'd suggest not to stop on MVC. There are many interesting design patterns that might or might not be suitable for your particular needs. MVVM was (is?) quite popular. Lately unidirectional flow seems to be the new hot thing. Inspired by JS's Redux. It is a very much departure from MVC and I suggest checking this project out https://github.com/ReSwift/ReSwift if you are interested.

Sadly like any architectural question there is no right and wrong - there are only things that work for you and your team or not. And I'd love to hear other perspectives before question gets closed as "too broad" 🙂

Sorry if this is completely not what you asked for, let me know if there are any particular aspects you'd like to discuss.

like image 33
Sash Zats Avatar answered Nov 16 '22 07:11

Sash Zats


  • You can have one ViewController

  • Have UIViews separated to ViewController. It should not be tightly coupled with viewController.

  • Have your View login in UIView class itself. Create its own delegate and protocols. and use it in any viewcontroller.

  • add viewcontroller to viewcontroller is not a good solution.

  • You can also move to MVVM where every view will have its logic part in its View Model, But it can be confusing at beginning.

  • Try to make UIViews separated to ViewControllers.

like image 1
coreDeviOS Avatar answered Nov 16 '22 05:11

coreDeviOS