Side menu controller - swift

I am working on a YouTube project and the problem is that I want to add a side menu and I know how to do that. However, when I press a cell I don't want it to load a new view. Instead, I want it to update my table view because I don't want 20 different views that do nearly the same thing and just load different videos. Any idea how I can achieve my goal?
I was thinking of using this side menu: https://github.com/John-Lluch/SWRevealViewController
Coding in swift

You could create a custom class that does all the tableview initialization as a BaseClass and make childClass for different Menus. Instead of:
class menu1: UITableViewController {
}
class menu2: UITableViewController {
}
Use this:
class BaseTableViewController: UITableViewController {
}
class menu1: BaseTableViewController {
}
class menu2: BaseTableViewController {
}
Reuse the class and make a fetch function content for cell with customizable input parameters. You could refer letsbuildthatapp youtube tutorial series for reference. It has a letsbuildyoutube app series.

Related

Swift best practice: How to keep track of different objects?

I'm wondering what is a good solution to keep track of different objects of same type.
I have this function:
private extension MenuButtonsViewController {
// TODO: Find a way to find correct button based on MenuItem
func buttonFor(for menuItem: MenuItem) -> EmojiButton? {
guard let subViews = stackView.subviews as? [EmojiButton] else {
return nil
}
let button = buttonFactory.makeEmojiButton(title: menuItem.icon)
for subView in subViews where subView == button {
return subView
}
return nil
}
}
I have an array (UIStackView) with a varying number of buttons (EmojiButton). The buttons are created with content from MenuItem.
I'm looking for a good and clean solution how to find and remove a particular button from the stackView array, based on a MenuItem.
So far I had three ideas:
To create a new object, initalized with same values as the one to remove, and then match using ==. (Solution above). This didn't work.
To add an id to all buttons, and then a corresponding id to the MenuItem object. But this doesn't seem like an elegant solution to have to add that everywhere, and expose this variable from the button object.
Maybe store the button in a wrapper class (like MenuItemButton) with an id to match to, or by storing the MenuItem object so I can match against that.
Any ideas? How is this usually done?
If MenuItem and EmojiButton inherit from UIView, you can make use of the tag property that is available on all UIView's.
You first need to assign a unique tag value to each of your MenuItem's.
You then need to assign this same value to the corresponding Emoji button's tag property. (This would be a good thing to do in your factory.)
Having done that, you can modify your function as follows:
//assumes MenuItem and EmojiButton inherit from UIView
func buttonFor(for menuItem: MenuItem) -> EmojiButton? {
return stackView.viewWithTag(menuItem.tag) as? EmojiButton
}

Best practice MVVM navigation using Master Detail page?

I want to follow the MVVM pattern as much as possible, but I don't know if I am doing the navigation quite well. Note that I am using a MasterDetail page and I want to maintain the Master page, changing only the Detail side when I navigate.
Here is the way I navigate from my ViewModel. In this example, from ViewModelOne to ViewModelTwo:
public class ViewModelOne : ViewModelBase
{
private void GoToViewTwo()
{
var viewTwo = new ViewTwo(new ViewModelTwo());
((MasterView)Application.Current.MainPage).NavigateToPage(viewTwo);
}
}
MasterView implementation:
public class MasterView : MasterDetailPage
{
public void NavigateToPage(Page page)
{
Detail = new NavigationPage(page);
IsPresented = false;
}
}
ViewTwo implementation:
public partial class ViewTwo : PageBase
{
public MenuView(ViewModelTwo vm)
: base(vm)
{
InitializeComponent();
}
}
PageBase implementation:
public class PageBase : ContentPage
{
public PageBase(ViewModelBase vmb)
{
this.BindingContext = vmb;
}
}
Is this the best approach (and best performance) for do the navigation? When I do some navigations, the app starts to run slower and maybe there is something I am not doing fine.
Is this the best approach for do the navigation showing always a MasterDetail page?
Thanks.
I think you're certainly on the right track, however there are a few issues here:
Firstly, you should not be instantiating Views in your view model. As soon as your View Model becomes aware of the view, then you've pretty much broken the pattern.
var viewTwo = new ViewTwo(new ViewModelTwo());
Your view creation should be the responsibility of the master view. In fact, you don't even need to worry about creating views, as you can use a DataTemplate for that. I'll explain that later.
Firstly, we need to separate your View Models from the Views, here is what I propose:
You'll need some kind of base class or interface for your view models in order to keep things generic, you'll see why in a moment. Let's start out with a simple example:
public abstract class ViewModel : INotifyPropertyChanged
{
public event EventHandler OnClosed;
public event EventHandler OnOpened;
//Don't forget to implement INotifyPropertyChanged.
public bool IsDisplayed { get; private set; }
public void Open()
{
IsDisplayed = true;
//TODO: Raise the OnOpened event (Might be a better idea to put it in the IsDisplayed getter.
}
public void Close()
{
IsDisplayed = false;
//TODO: Raise the OnClosed event.
}
}
This of course is a very simple base view model, you can extend on this later, the main reason for this is to allow you to create a master view model which will be responsible for displaying your current page. Here's a simple example of a master view model:
public class MasterViewModel : INotifyPropertyChanged
{
//Don't forget to implement INotifyPropertyChanged.
public ViewModel CurrentPage { get; private set; }
public MasterViewModel()
{
//This is just an example of how to set the current page.
//You might want to use a command instead.
CurrentPage = new MovieViewModel();
}
//TODO: Some other master view model functionality, like exiting the application.
}
Please note that INotifyPropertyChanged would probably be better in some kind of base class, instead of having to re-implement the same code over and over.
Now the MasterViewModel is pretty simple, it just holds the current page, however the purpose of having the master is to allow for application level code to be executed, like closing the app, that way you're keeping this logic away from your other view models.
Right, now onto the good stuff.
Your detail has a relationship to it's parent, therefore it would make sense to say that it is the responsibility of the parent to manage it. In this case, your master-detail view model would look something like this:
public class MovieViewModel : ViewModel
{
protected PickGenreViewModel ChildViewModel { get; private set; }
public MovieViewModel()
{
ChildViewModel = new PickGenreViewModel();
//TODO: Perhaps subscribe to the closed event?
}
//Just an example but an important thing to note is that
//this method is protected because it's the MovieViewModel's
//responsibility to manage it's child view model.
protected void PickAGenre()
{
ChildViewModel.Open();
}
//TODO: Other view model functionality.
}
So, now we've got some kind of view model structure here, I bet you're asking "What about the views?", well, that's where the DataTemplate comes in.
In WPF, it's possible to assign a view to a Type, for example, you can assign the MovieView to the MovieViewModel in XAML, like this:
xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"
...
<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
<Views:MovieView/>
</DataTemplate>
Ok great!, now to get the Master View to actually display the current page's view, you simply need to create a ContentPresenter, and bind it's Content to the CurrentPage. Your Master View will look something like this:
<Window
...
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
<ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
<ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>
To extend this further, it's not only the MasterView that needs to contain a ContentPresenter for it's child, it is also the MovieView which needs one for it's child PickGenreViewModel. You can use the same method again:
<Grid>
<!-- The main view code for the movie view -->
...
<Border Visibility="{Binding ChildViewModel.IsDisplayed, Converter=...">
<ContentPresenter Content="{Binding ChildViewModel}"/>
</Border>
</Grid>
Note: Use a boolean to Visibility converter to determine whether to display the child content.
Using this method you don't have to worry about instantiating any views, as the DataTemplate and ContentPresenter handles that for you, all you need to worry about is mapping the view models to the appropriate view.
Phew! That was a lot to take in.
The main points to take away from this are:
You shouldn't be creating views in your view models, remember, UI is UI, Data is Data.
View model responsibilities lie with whoever owns them, for a parent-child relationship, it makes sense to let the parent manage the child, as opposed to a view model manager.
A final note is that there are certainly more than one other ways of achieving this, as I just mentioned, some kind of view and view model manager to be responsible for creating/removing views and view models.

Understanding Interface Builder - Swift

Alright, so I'm not trying to do a whole lot at this stage. I have created a Swift class that is supposed to update an NSPopUpButton with a list of available serial ports. I've finalized the code for retrieving the list of ports and created a window in interface builder including the popup button but I can't get the two to work together.
I've tried editing the 'custom class' field of the window to 'Serial' to match my class but that gives me errors in the debugging window when running the program. I've tried to link the two with the command+drag tool with varying success but no matter what I try, I can't manage to update the button of my program.
How should I be doing this? What am I missing?
import Foundation
import Cocoa
class Serial {
init() {
}
#IBOutlet weak var serialListPullDown: NSPopUpButton!
func refreshSerialList(defaultprompt: String) {
//Initialize ORSSerialPortManager
let portManager : ORSSerialPortManager = ORSSerialPortManager.sharedSerialPortManager()
var availablePorts : NSArray = portManager.availablePorts
//Erase entries from popup field
serialListPullDown?.removeAllItems()
for port in availablePorts as [ORSSerialPort] {
//Add ports to popup window
var Port = "\(port.path)"
serialListPullDown?.insertText(Port)
}
}
}
You need to change Serial to a subclass of UIViewController (assuming that That window is a UIViewController)
class Serial: UIViewController {}
then change the Custom class to Serial again. It shouldn't have errors anymore
Then start cmd+drag to connect your NSPopupButton object

How to bind view model to a UserControl in MVVMCROSS?

I am using the excellent Mvvmcross and Ninja Coder for Mvvmcross for building a cross platform app. For my windows store app I have created a view and a view model using Ninja coder. I have also created a UserControl which will be referenced in the view. Hence I need to bind the same viewmodel to the User control also. I have been trying to set the Data context of the user control to the singleton instance of viewmodel. I have set the data context of the user control like below.
public sealed partial class SearchResultsGridViewControl : UserControl
{
private SearchresultsViewModel _viewModel;
public SearchResultsGridViewControl()
{
this.InitializeComponent();
_viewModel = Mvx.IocConstruct<SearchresultsViewModel>();
this.DataContext = _viewModel;
}
}
But when I refer this User Control in my main view, it throws an error in XAML saying "Object Reference not set to an instance of an object. Cannot create an instance of SearchResultsGridViewControl".
This is my viewmodel:
public class SearchresultsViewModel : BaseViewModel
{
private ISearchResultsService _searchResultsService;
public SearchresultsViewModel(ISearchResultsService searchResultsService)
{
_searchResultsService = searchResultsService;
var items = _searchResultsService.DisplaySearchResults();
SchoolDetails = new ObservableCollection<School>(items);
}
private ObservableCollection<School> _schoolDetails;
public ObservableCollection<School> SchoolDetails
{
get { return _schoolDetails; }
set
{
_schoolDetails = value;
RaisePropertyChanged(() => SchoolDetails);
}
}
public ICommand RefineCommand
{
get
{
refineCommand = refineCommand ?? new MvxCommand(FilterSearchResultsBasedOnRefine);
return refineCommand;
}
}
public void FilterSearchResultsBasedOnRefine()
{
SchoolDetails = new ObservableCollection<School>(_searchResultsService.FilterSchoolsBasedOnRefine(MidDayMeals, PlayGround, DigitalClassroom, DayBoarding, TransportationFacility));
}
}
The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated. And I am guessing its because of that error earlier in setting the data context of the user control to view model. Please let me know what could be going wrong. I have been banging my head about it for days.
I've been using MVVMCross with Windows Store fairly recently. Without looking back at my code, I'm pretty sure that the Datacontext will inherit from it's parent unless overridden.
So as long as the MvxPage that you have presented has a viewmodel, any user control that you add to it, either in XAML or in code-behind, should share the same data context. If you are looking at doing some MVVMCross data-binding from the User Control, you should probably make sure your User Control implements IMvxStoreView, and ensure that the ViewModel property is set to the value of DataContext.
Hope that help.
Cheers,
Tristan
I think your first problem "Object Reference not set to an instance of an object" is a design time only issue - because you are trying to set the viewmodel using Mvx. at design time. You can workaround this issue if you want to by using design time viewmodels and possibly also by using one of the design time helpers (see https://github.com/MvvmCross/MvvmCross/blob/v3.1/CrossCore/Cirrious.CrossCore.Wpf/Platform/MvxDesignTimeHelper.cs).
I've no idea what your second problem is "The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated" - this sounds like an issue either in your xaml or in the results returned from FilterSearchResultsBasedOnRefine. From the current level of detail, I can't see what it is. My "gut feeling" is that the problem won't be MvvmCross specific - it'll just be a general Mvvm/data-binding issue.

UIButton subclass displays wrongly

I have a button that's created via a NIB file. I've derived a class from UIButton, replaced the class name in the NIB file.
Now my button displays with no background. The text is there, the text font and color are right, and it reacts to taps as expected, but it's as if the background is transparent. In the NIB, it's not transparent - I did not change any of the properties other than the class name.
The subclass is trivial - it overrides nothing (for now). Please, what am I doing wrong?
The reason I need a subclass of UIButton is because I want to be able, under certain circumstances, to drag text from the button to elsewhere. If there's an alternative way to handle drag and drop in a UIKit provided view, I'm willing to hear.
Check the states of the button in your NIB file.
It's possible that you are looking at the "active" state or something rather than the more common UICONTROLSTATENORMAL.
Honestly not sure what's wrong with the subclass, but the 'Net (including SO) is full of cautionary tales about subclassing UIButton, and how you shouldn't.
So I'll go with method swizzling on the four touch processing methods. The following function replaces the provided method of a button with my implementation (taken from the MyButton class), while saving the old one in the system button class under a different selector:
//Does NOT look at superclasses
static bool MethodInClass(Class c, SEL sel)
{
unsigned n,i ;
Method *m = class_copyMethodList(c, &n);
for(i=0;i<n;i++)
{
if(sel_isEqual(method_getName(m[i]), sel))
return true;
}
return false;
}
static void MountMethod(Class &buc, SEL SrcSel, SEL SaveSlotSel)
{
IMP OldImp = [buc instanceMethodForSelector:SrcSel];
IMP NewImp = [[MyButton class] instanceMethodForSelector:SrcSel];
if(OldImp && NewImp)
{
//Save the old implementation. Might conflict if the technique is used
//independently on many classes in the same hierarchy
Method SaveMe = class_getInstanceMethod(buc, SaveSlotSel);
if(SaveMe == NULL)
class_addMethod(buc, SaveSlotSel, OldImp, "v#:##");
else
method_setImplementation(SaveMe, OldImp);
//Note: the method's original implemenation might've been in the base class
if(MethodInClass(buc, SrcSel))
{
Method SrcMe = class_getInstanceMethod(buc, SrcSel);
if(SrcMe)
method_setImplementation(SrcMe, NewImp);
}
else //Add an override in the current class
class_addMethod(buc, SrcSel, NewImp, "v#:##");
}
}
And call it so:
Class buc = [bu class];
MountMethod(buc, #selector(touchesBegan:withEvent:), #selector(MyButton_SavedTouchesBegan:withEvent:));
MountMethod(buc, #selector(touchesCancelled:withEvent:), #selector(MyButton_SavedTouchesCancelled:withEvent:));
MountMethod(buc, #selector(touchesEnded:withEvent:), #selector(MyButton_SavedTouchesEnded:withEvent:));
MountMethod(buc, #selector(touchesMoved:withEvent:), #selector(MyButton_SavedTouchesMoved:withEvent:));
This has the disadvantage of mounting the said methods for all buttons, not just for the ones desired. In the MyButton's implementation, there's an additional check if the drag-drop functionality is to be enabled for this particular button. For that I've used associated objects.
One fine point is that touchesXXX methods are implemented in the UIControl class, not in the UIButton. So my first, naive implementation of swizzling would replace the method in UIControl instead of the button class. The current implementation does not assume either way. Also, it makes no assumptions about the run-time class of buttons. Could be UIButton, could be anything (and in real iOS, it's UIRoundedRectButton).