I am making an app for iPad using RXSwift and MVVM.
I have a UIViewController with a UICollectionView and a ViewModel which acts as the dataSource and the delegate for the collectionView.
Part of the collection cells' functionality is that when a button is tapped to present a popover. Now with the newer popover functionality in iOS 9 (possibly earlier) you need to present the view normally within the view controller, and modify the popoverPresentationController.
Now, as far as i'm aware you are not able to present a UIViewController from a UICollectionViewCell. Makes sense.
But the only way i thought to do this would be to have a delegate that points to the ViewController.
Looking at the class diagram (attached), the viewModel would have to set the delegate upon cell dequeue. To do that the ViewModel would have to know what ViewController to set as the delegate which i'm fairly sure goes against the point of the viewModel. According to MVVM (for iOS) the view model should not know about the view controller. The view controller can know about the view model.
And so my question is what would be the best way to do this following MVVM? If it requires moving the dataSource/Delegate to a different class i'm all for it.
I think view model shouldn't be aware of the button being tapped at all. Handling touch events belongs the view layer, as well as presenting the popover.
This also indicates that your view model shouldn't probably be a UICollectionViewDataSource. So it is coupled with RootCollectionViewCell, which is a view. Unfortunately, this coupling is hard to avoid because Apple designed UICollectionViewDataSource this way. You can either extract a separate class as the data source, or leave the data source methods in the view controller (which belongs to the view layer in MVVM on iOS).
Using RxCocoa, you can even avoid implementing UICollectionViewDataSource methods at all. Take a look at UICollectionView+Rx extensions. There is also an example in RxSwift repository (table view cell containing a collection view).
For passing the button taps to the view controller, you can use rx_tap Observable and expose it in the cell's interface. Then you can subscribe to the resulting Observable in the view controller (or in the separate data source class):
//in the cell class
var buttonTapped : ControlEvent<Void> {
return button.rx_tap
}
//in the data source
cell.buttonTapped.subscribeNext {
//show the popover
}.addDisposableTo(cell.disposeBag)
As described in this answer, you should avoid subscribing many times to the same Observable when the cell is reused. That's why cell.disposeBag is used in the code above. You should also re-create cell's disposeBag in its prepareForReuse method:
class RootCollectionViewCell: UICollectionViewCell {
var disposeBagCell:DisposeBag = DisposeBag()
...
override func prepareForReuse() {
disposeBagCell = DisposeBag()
}
}
Related
I have been creating my Swift project completely programmatically thus far. I have created detailed Navigation Controllers and Table View Controllers without touching the StoryBoard (SB) once. I have come to a point where I would like to pass data from a TableViewCell click onto another ViewController, however, I believe that I need to use a segue and create/identify it inside of the SB. Since this application has become pretty complex over time, it has become quite difficult to mimic all of the view controllers inside of the SB, and creating any changes inside of the SB is not reflecting any changes inside of the Views on the simulator (I have paired the views inside of the SB to their respective class, yet nothing is working. Even the segue is not being recognized when the identifier matches up). Therefore, I have a couple of questions.
Is it possible to perform a segue programmatically? In other words, is it possible to pass data from one view controller to another without touching the story board?
Is there a simple guide or technique one can follow in order to mimic their code inside of the StoryBoard? With a simple application, it shouldn't be too difficult. However, is there any other way in order to portray the application in the StoryBoard if someone has been creating it completely programmatically?
Interface Builder (IB) is just a GUI for programmatic development. Anything you can do in IB you can definitely do programmatically but not everything you can do programmatically you can do in IB, because IB is just for interface building—and just some interface building, not all.
segue is just IB terminology. In iOS, you can only display a view controller three ways: push (via UINavigationController), present (via a presentation object vendor UIViewControllerTransitioningDelegate), or show (a more generic approach to displaying a view controller).
Since you're new to programmatic iOS development (the best kind, IMO), a quick 101:
class ProgrammaticViewController: UIViewController {
override func loadView() {
// do not call super.loadView()
// most IB developers don't even know this method exists
// because this is where IB does its work
// add all of your view objects here (scroll views,
// table views, buttons, everything)
setView()
addTableView()
...
}
override func viewDidLoad() {
super.viewDidLoad()
// do call super.viewDidLoad(), however
// do your post-view setup here, like adding observers
// or fetching data
// this method is called after the entire view
// has been loaded into memory so consider that
}
// other lifecycle events that come after viewDidLoad where you
// can perform last-second work include viewDidLayoutSubviews(),
// viewWillAppear(), viewDidAppear(), etc.
deinit {
// do any cleanup here like deactivating timers, removing
// observers, etc.
}
// MARK: Methods
func setView() {
// if you're creating your view controller programmatically,
// you must create the view controller's actual view property
// and it must be done before adding any subviews to the
// view controller's view
view = UIView()
view.frame = UIScreen.main.bounds
view.backgroundColor = UIColor.white
}
}
If you are using auto layout (and you probably should be), you don't always need to explicitly set the frame of the view with view.frame = UIScreen.main.bounds. If it's the root view controller of the app, it's not needed—the window owns that view controller and it will set the frame. If you've then put a UINavigationController in the root and you're using auto layout, none of the view controllers you push to ever need their frame set as well. Therefore, the only real time you need to explicitly set the view's frame using something like view.frame = UIScreen.main.bounds is when you present a view controller modally. This is because a modally-presented view controller is not owned by the window or a navigation controller; it exists temporarily inside a transient container view. In this case, you must set its frame.
Passing data forward programmatically is simpler than it is for IB developers. Just instantiate a view controller, inject one of its (non-private) properties with a value, and push, present, or show it. Here's what a UITableView delegate for didSelectRowAt may look like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let p = indexPath.row
let detailViewController = SomeDetailViewController()
detailViewController.someProperty = searchResults[p]
navigationController?.pushViewController(detailViewController, animated: true)
}
And this is obviously doable because you've created that property in that view controller and not given it a private access modifier.
class SomeDetailViewController: UIViewController {
var dataObject: SomeType? // injectable
private var notInjectable: SomeType?
}
UPDATE: IB is clearly not the future of iOS development as far as Apple is concerned, with the introduction of SwiftUI, which is another way of programmatically developing iOS. Personally, I hate GUI programming and I'm happy to see it slowly phase out.
Is it possible to perform a segue programmatically?
Sure. UIStoryboardSegue has initializers that you can use to create a segue, and it has a perform() method that you can call to make the segue do it's thing. But segues are mostly a tool that makes it easier to transition between scenes in a storyboard, so there's little reason to want to do that if you're not using storyboards in the first place.
In other words, is it possible to pass data from one view controller to another without touching the story board?
Again, yes. Storyboards didn't even exist until iOS 5, and you can bet people were writing apps with transitions between view controllers well before that. The answer you've already gotten about how to do that is fine. More generally, though, view controllers are just objects, and all that one needs to send information to another is a valid reference to the destination controller.
However, is there any other way in order to portray the application in the StoryBoard if someone has been creating it completely programmatically?
If you mean that you want to switch over to using storyboards, then yes, there are some good ways to do that. If you mean that you want to somehow represent what your app is doing programmatically inside a storyboard but continue to do everything programmatically, then no, there's no way to do that (and really no point in doing it).
Transitioning to storyboards doesn't have to be an all or none kind of thing. You can model just one view controller in a storyboard and load it as needed using init(name:bundle:) (to initialize the storyboard itself) and instantiateViewController(withIdentifier:) (to load a particular view controller), and then just use that view controller the same way you do now. All the code that you use to set up that view controller and it's view hierarchy can then be removed, since you're now loading it from the storyboard instead. Once that's working, repeat the process for other view controllers, expanding your storyboard and excising the setup code as necessary.
I've been implementing the MVVM paradigm for a while now while adopting ReactiveCocoa in certain parts of the project. I have a simple question about the lifetime of an object related to a view.
Imagine that whenever the View disappears from the screen, the View Model needs to update something in an object. Should this updates be called by the ViewController or can the View Model observe, for example, the viewWillDisappear Selector in the ViewController and react to it? Would that be a bad practice?
You use MVVM pattern in order to decouple view (and view controllers, which in Cocoa are also considered a part of the View layer) from the model. That means view model should not know anything about the view controller.
As described in this post, ideally you shouldn't even import UIKit in your view model.
In other words, a view model should be reusable for displaying the same data in different ways: you may want to display the data in a view controller and a plain UIView subclass somewhere else (think about having an PersonViewModel in PersonTableViewCell and in a PersonDetailsViewController which is shown after tapping a cell - I think it's a pretty common scenario).
If you somehow observe viewWillDisappear in the view model, it is tightly coupled to UIViewController subclasses and can't be used with UIView subclasses.
Updates to the view model should be called in the view controller in a following way:
- (void)viewWillDisappear:(BOOL)animated
[super viewWillDisappear:animated];
[self.viewModel updateStuff];
}
When I am implementing a viewController with CollectionView, whether should I drag drop a CollectionViewController from storyBoard then creating a MyCollectionViewController class, which is a subclass of CollectionViewController?
OR should I just use a normal viewController, add a collection view to it,and implement its dataSource and delegate protocol?
I know one of the advantage for later is that you may have multiple CollectionViews under one ViewController? But what other pros and cons?
And how it applies to the TableViewController, as they are quite similar to each other.
UICollectionViewController inherits from UIViewController, so it essentially is a UIViewController with a UICollectionView and some built-in functionality. Per the documentation, this functionality is:
If the collection view controller has an assigned nib file or was
loaded from a storyboard, it loads its view from the corresponding nib
file or storyboard. If you create the collection view controller
programmatically, it automatically creates a new unconfigured
collection view object, which you can access using the collectionView
property.
When loading a collection view from a storyboard or nib file, the data
source and delegate objects for the collection view are obtained from
the nib file. If a data source or delegate is not specified, the
collection view controller assigns itself to the unspecified role.
When the collection view is about to appear for the first time, the
collection view controller reloads the collection view data. It also
clears the current selection every time the view is displayed. You can
change this behavior by setting the value of the
clearsSelectionOnViewWillAppear property to NO.
I prefer to always subclass UIViewController and add a UICollectionView in storyboard. This means that my subclass has to implement the UICollectionViewDataSource and UICollectionViewDelegate protocols and I have to set them in storyboard, and I have to make sure that when the view controller appears that I reload the collection view. But because I implement the behavior myself, it feels like I have more control. If you tend to use the regular, default behavior, then perhaps go with the UICollectionViewController to save time.
I feel exactly the same way about UITableViewController and UIViewController with a UITableView - I prefer the latter for exactly the same reason.
If you are sure that your UI will have only CollectionView, use CollectionViewController.
This rule applies for tableViews also.
Personally I started to follow a different approach in general. I use plain UIViewController to wich I add UITableView or UICollectionView. In the controller I only create a IBOutlet for the view.
class MyViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
}
Next I create a separate object as my datasource
class MyDataSource: NSObject, UITableViewDataSource {
init(with tableView: UITableView){
//Register cells, configure tableview, etc
}
numberOf...
cellForRowAt...
}
Then in my view controller, i have a lazy var with the datasource:
lazy var dataSource: MyDataSource = { return MyDataSource(with: self.tableView) }()
And finally in viewDidLoad I set the datasource to the tableview:
tableView.dataSource = dataSource
By doing this, you can keep your viewcontroller really clean and small as it should be. You prevent the MVC (massive view controller) problem and follow the principle of single responsibilities. Also your datasource is now reusable across multiple view controllers.
Is there any way that could send the value in Class of ViewController to Class of View?
Because I want to make a drawing board and I made a modal scene to set values about color, width .
I know how to send the value in modal scene to my ViewController , but now I need use those value in Class of View , or not Class of ViewController.
Sounds like you need to devise a protocol to open a delegation channel between the two classes. Then when the modal VC wants to send data to its delegate (its presenter, in this case), it can of its own volition.
The View Controller can have a reference to the View, therefore the View Controller can simply pass the values to View by calling a View's method or updating View's properties. This is a common pattern: View defines a Protocol and data/requests from View to ViewController go through this Protocol (the ViewController acts as delegate of View); and ViewController owns the View.
For inspiration on how to implement a farily decoupled design applying this pattern, you can have a look at documentation on UICollectionView and UICollectionViewController.
Other options, depending on what design you need, are Key-Value Observing or Notifications.
I have a Table View Controller that displays information using JSON. I want to change the styling of my app, and I don't want it to have that "table" view that it has now. Whats the easiest way to change my Table View Controller to a regular View Controller, the biggest problem I have is that the code uses a tableView and I dont know how to get it to work as a regular view controller.
I using a Storyboard with a TableViewController thats linked to a controller called UpcomingReleasesViewController.
I want my app:
To look like this:
My original answer was assuming you just wanted to convert from a UITableViewController to a UIViewController. Looking at your screen snapshots, I infer you really want to switch from a UITableViewController to a UICollectionViewController (which is an iOS 6 feature than allows you to do precisely what you want).
In that case, change your base class to UICollectionViewController, and replace your UITableViewDataSource methods with UICollectionViewDataSource methods. And then redesign your scene using a Collection View Controller.
See the Collection View Programming Guide for more information. Or see WWDC 2012 sessions Introducing Collection Views or Advanced Collection Views and Building Custom Layouts.
If you need to support iOS versions prior to 6, then you have to do this collection view style coding yourself manually, putting your image views on a scroll view and using a standard UIViewController. It require more effort than using a collection view, but can be done.
Original answer:
If this view controller will have a table view on it, but you just want to add more controls to the scene? If so, just change the view controller's base class from UITableViewController to UIViewController, create the new scene, add a table view to it, and specify the table view's delegate and data source to be the view controller:
Also, make sure you define an IBOutlet for your table view (and if you call it tableView, that will minimize any coding changes needed).
If you do that, you can quickly convert a UITableViewController based controller to a UIViewController with minimal code changes.
If you're looking to make something like your new UI mockup, look into UICollectionView. You'll see many of the same concepts (i.e. dataSource, delegate method signatures are similar) that are used in UITableViews used in the collectionView API.