I'm new to iOS programming, I read a lot of tutorials and forums but I still can't figure the best way to manage a project.
What I want is the iPad screen to display a CollectionView and a TableView side by side.
Actions in the CollectionView should change TableView content. SplitViewController won't do the job because of the fixed size of the split.
For now I'm using Storyboard, I created a ViewController and added two ContainerViews in it. Each container is linked by an XCode generated segue to a view controller (LeftViewController and RightViewController).
I'm trying to figure the smartest way to manage actions on the LeftViewController and send changes to the RightViewController.
I would like to use Storyboard that seems more elegant, but I'm not sure how to implement this.
Assuming you know the way to establish delegate methods (#protocol, see here for links), the key elements will be grabbing the two viewControllers embedded in containers as they are being loaded, setting the primary viewController as delegate, and sending the messages when something changes. For starters, if communication needs to flow both ways between controllers, set up an instance variable for each VC.
Given VCPrime, CollectionVC, and TableVC:
First, in storyboards, name each of your segues (from the containerViews to the VCs). In VCPrime, implement prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"collection"]) {
self.collectionVC = (CollectionVC *)[segue destinationViewController];
self.collectionVC.delegate = self;
}
if ([segue.identifier isEqualToString:#"table"]) {
self.tableVC = (TableVC *)[segue destinationViewController];
self.tableVC.delegate = self;
}
}
You must also implement the delegate methods in VCPrime, and declare CollectionDelegate, TableDelegate, or however you named them.
In CollectionVC, when someone selects something (or whatever), check that the delegate responds to your delegate method, then send that message:
if ([self.delegate respondsToSelector:#selector(doSomething)]) [self.delegate doSomething];
Then alter TableVC in the method that is called.
This is just a quick rundown. The internets are alive with great code examples.
I think I might have an improvement in Swift (the answer is old but I was facing this problem a day ago). I re-implemented the above in Swift, but with a twist. Instead of setting the VCPrime as a delegate for for both CollectionVC and TableVC, I would make the TableVC a delegate of CollectionVC. That is because CollectionVC needs to control TableVC. In general we can call a "master VC" the one that controls and the "delegate VC" the one that is controlled. In order to make sure that both VC are actually instantiated when I set one as the delegate of the other, I use Swift optionals and optional bindings. Here is a sample of code" (note that the master needs to have a "delegate" property and you might want to declare the appropriate protocols):
import UIKit
class ContainerController: UIViewController {
private var masterViewController: myMasterViewController?
private var delegateViewController: myDelegateViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "myMasterViewController" {
masterViewController = segue.destinationViewController as? myMasterViewController
} else if segue.identifier == "myDelegateViewController" {
delegateViewController = segue.destinationViewController as? myDelegateViewController
}
if let master = masterViewController, delegate = delegateViewController {
println("Master and delegate set")
master.delegate = delegate
}
}
}
Related
My swift code below saves 3 images. What I want to do is overwrite iamgedata2 is func press. Imagedata2 should be replaced with Gwen. So the order should be Gwen Gwen Gwen instead of Gwen gwen2 Gwen. I don't know what really to put in func press to achieve this goal.
import UIKit; import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .orange
let gwen = UIImage(named: "blank")
let gwen2 = UIImage(named: "g.jpg")
if let imageData = gwen.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData)
}
if let imageData2 = gwen2.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData2)
}
if let imageData3 = gwen.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData3)
}
}
#objc func press(){
CoredataHandler.shareInstance.saveImage(data: 1)
return
}
}
class CoredataHandler : NSManagedObject {
static let shareInstance = CoredataHandler()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func saveImage(data: Data) {
let imageInstance = Information(context: context)
imageInstance.pic = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
}
If you want a Core Data entity that can store more than one image, you have a few options:
Declare multiple image properties
Instead of just having a pic property, have more than one. As many as you like. Name them pic1, pic2, pic3, etc, or whatever seems best for your app. In code, read or write whichever makes sense at the time.
This is easy but not flexible, since your code can only save up to the number of attributes you declare in the model.
Use an array property with transformable
With a transformable attribute you can store any data that can be converted to a Data. In your case you'd do something like this:
Two things to notice: The type is transformable, and the custom class is [Data]. Xcode will generate code where the property type is [Data]?. You can save as many Data blobs as you want in it, representing however many images you want.
This is also easy but may use a lot of memory, because you can't access one image without loading all of them into memory at once. If you always load all of them anyway then it's no different. If you often load only one of them, this technique might use a lot more memory (e.g. 4 images would mean around 4x as much memory used).
Use a separate entity to hold the image and a to-many relationship
With this approach you'd create a new Core Data entity that only holds the image. Your existing entity would have a to-many relationship to this entity. You'd create as many image-only instances as you want, and the relationship would mean they were all available at any time.
You would probably want to make sure the to-many relationship is ordered, otherwise the images would be an unordered set that could be in any order.
This is a little more complex to write but it's flexible and doesn't have the potential memory problems of other approaches.
I want to pass a UIimage from viewController to another without using segue and i keep searching for this problem for weeks. Also, i tried to do it using notificationCenter, delegate and protocol but it didn't work with me so help please.
This is a screenShot describe what i want to do exactly.
enter image description here
Also, this is my code for the viewController which i want to pass the thor image from it when the pass Label button is clicked .
import UIKit
protocol UserChosePhoto {
func userHasChosen(image: UIImage)
}
class passingDataViewController: UIViewController {
#IBOutlet var Label: UILabel!
#IBOutlet var image1: UIImageView!
var image: UIImage? = nil
var delegate: UserChosePhoto? = nil
override func viewDidLoad() {
super.viewDidLoad()
image1.image = image
}
#IBAction func passLabel(_ sender: Any) {
func tapped() {
if (delegate != nil) {
self.delegate!.userHasChosen(image: image1.image!)
}
}
}
}
and this is the code for the viewController which i want to display thor image in it .
import UIKit
class receivingDataViewController: UIViewController, UserChosePhoto {
#IBOutlet var label2: UILabel!
#IBOutlet var image2: UIImageView!
var usedImage: UIImage? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
func userHasChosen(image: UIImage) {
usedImage = image
print("delegation: \(image)")
}
}
I'm seeing a few things missing, along with some Swift coding conventions not being followed. So let's start with your UserChosePhoto protocol, which is pretty good. But - why not make your code readable?
protocol PassingDataViewControllerDelegate {
func userHasChosen(image: UIImage)
}
Sure, what you have will work, but adding Delegate to the end of your name (along with which VC you are using this with) make everyone know what you are doing.
Now you need to properly set up your first view controller:
class PassingDataViewController: UIViewController {
var delegate: PassingDataViewControllerDelegate? = nil
func tapped() {
if (delegate != nil) {
self.delegate!.userHasChosen(image: image1.image!)
}
}
}
Again, very close to your code - I just capitalized your view controller's class name. This is how Swift coders do it. Oh, and since I changed the name of the portal, that needed to be changed to.
But there's one more thing - you have a second way to code this:
class PassingDataViewController: UIViewController {
var delegate: PassingDataViewControllerDelegate! = nil
func tapped() {
delegate.userHasChosen(image: image1.image)
}
}
This results in a couple of things. Obviously, the delegate call is simpler. But more importantly, if you don't set up things correctly in your second view controller, your app crashes. (Sometimes, that's actually a good thing!)
Here's where I think you didn't do things right - the second VC:
class ReceivingDataViewController: UIViewController, PassingDataViewControllerDelegate {
var usedImage: UIImage? = nil
let passingDataViewController = PassingDataViewController()
override func viewDidLoad() {
super.viewDidLoad()
passingDataViewController.delegate = self
}
func userHasChosen(image: UIImage) {
usedImage = image
print("delegation: \(image)")
}
}
Now, several things.
First, note that I'm creating an instance of the first VC - that's needed. Delegation is always a 1:1 kind of communication. What is still missing (because you haven't mentioned it in your question) is how the first VC is being presented. I know you don't want to use a segue, but how is your app "flow" going from the first to the second view controller? Usually without a segue that means presenting it modally (like you would with UIImagePickerController). You can also add both the view controller and it's view as a child VC and view in the hierarchy.
Second, I'm telling the first VC that the second VC is the it's delegate. If instead of using optionals (var delegate: PassingDataViewControllerDelegate? = nil) you force-unwrap things (var delegate: PassingDataViewControllerDelegate! = nil) you'd see your app crash because the second VC isn't the delegate for the first VC without passingDataViewController.delegate = self.
FINAL NOTE:
While I was writing this #TLG_Codin provided an answer. It could work - but *where is userImage declared as a public variable? In one of two places:
Globally, as in outside of a class. That's called a singleton, and be very, very careful on doing this. Since it's global, anyplace in your app can change it. That's why singletons are generally frowned upon.
Inside your first VC. The problem there is - how are you presenting this VC? Again, you've mentioned you don't want to use a segue. So... you're back to my note about presenting the first VC from the second or adding the VC and it's view to the hierarchy. Either way? Delegation works perfectly, and at the moment you wish to tell interested classes (in this case the second VC) that userImage has changed, you do.
Try using public variable. It will let you store anything you want in it and access it from anywhere in your project.
for example, put this line anywhere outside of any class:
public var userImage: UIImage? = nil
and then you can use it anywhere in your code by using userImage like any other variable.
Or, if you have multiple variables, you can create a struct with static variables:
struct sharedVariables {
static var userImage: UIImage? = nil
static var userList: [Any] = [64, "Hello, world!"]
static var integer: Int = 300
// Or add anything you want; just make sure to start with 'static'
}
And then you can use the variables in the struct like that:
sharedVariables.userList
Of course, there are more ways to do what you asked for, but this is the simplest one.
I am building composite WPF application using MVVM-light. I have Views that have ViewModels injected into them using MEF:
DataContext = App.Container.GetExportedValue<ViewModelBase>(
ViewModelTypes.ContactsPickerViewModel);
In addition, I have ViewModels for each View (Screens and UserControls), where constructor usually looks like this:
private readonly ICouchDataModel _couchModel;
[ImportingConstructor]
public ContactsPickerControlViewModel(ICouchDataModel couchModel)
{
_couchModel = couchModel;
_couchModel.GetContactsListCompleted+=GetContactsListCompleted;
_couchModel.GetConcatcsListAsync("Andy");
}
Currently, I have some performance issues. Everything is just slow.
I have 2 kind of related questions
What is the right way of calling DAL methods asynchronously (that access my couchdb)? await/async? Tasks? Because currently I have to write a lot of wrappers(OnBegin, OnCompletion) around each operation, I have GetAsyncResult method that does some crazy things with ThreadPool.QueueUserWorkItem , Action etc.
I hope there is the more elegant way of calling
Currently, I have some screens in my application and on each screen, there are different custom UserControls, some of them need same data (or slightly changed) from DB.
Questions: what is the right way to share datasource among them? I am mostly viewing data, not editing.
Example: On Screen A: I have Contacts dropdown list user control (UC1), and contact details user control(UC2). In each user control, their ViewModel is calling DAL:
_couchModel.GetConcatcsListAsync("Andy");
And on completion I assign result data to a property:
List<ContactInfo> ContactsList = e.Resuls;
ContactsList is binded to ItemsSource of DropDownListBox in UC1. The same story happens in UC2. So I end up with 2 exactly same calls to DB.
Also If I go to Screen B, where I have UC1, I’ll make another call to DB, when I’ll go to Screen B from Screen A.
What is the right way to making these interaction ? e.g. Getting Data and Binding it to UC.
Thank you.
Ad.1
I think you can simply use Task.Factory to invoke code asynchronously (because of that you can get rid off OnBegin, OnCompletion) or if you need more flexibility, than you can make methods async.
Ad. 2
The nice way (in my opinion) to do it is to create DatabaseService (singleton), which would be injected in a constructor. Inside DatabaseService you can implement some logic to determine whether you want to refresh a collection(call DAL) or return the same (it would be some kind of cache).
Then you can call DatabaseService instead of DAL directly and DatabaseService will decide what to do with this call (get collection from DB or return the same or slightly modified current collection).
Edit:
DatabaseService will simply share a collection of objects between ViewModels.
Maybe the name "DBCacheService" would be more appropriate (you will probably use it only for special tasks as caching collections).
I don't know your architecture, but basically you can put that service in your client application, so the plan would be:
Create DatabaseService.cs
[Export(typeof(IDatabaseService))]
public class DatabaseService : IDatabaseService
{
private List<object> _contacts = new List<object>();
public async Task<List<object>> GetConcatcsList(string name)
{
if (_contacts.Count == 0)
{
//call DAL to get it
//_contacts = await Task.Factory.StartNew(() => dal.GetContactsList(name));
}
else
{
//refresh list if required (there could be some condition)
}
return _contacts;
}
}
Add IDatabaseService to your ViewModel's constructor.
Call IDatabaseService instead of DAL.
If you choose async version of DatabaseService, then you'll need to use await and change your methods to async. You can do it also synchronously and call it (whenever you want it to be asynchronous) like that:
Task.Factory.StartNew(() =>
{
var result = dbService.GetContactsList("Andy");
});
Edit2:
invoking awaitable method inside Task:
Task.Factory.StartNew(async () =>
{
ListOfContacts = await _CouchModel.GetConatcsList ("Andy");
});
I 'm fairly new to Prism and I 'm currently re-writing one of our existing applications using Prism as a proof of concept project.
The application uses MVVM with a ViewModel first approach: our ViewModel is resolved by the container, and an IViewResolver service figures out what view it should be wired up to (using name conventions amongst other things).
The code (to add a view to a tab control) at the moment looks something like this:
var vm = (get ViewModel from somewhere)
IRegion reg = _regionManager.Regions["MainRegion"];
var vw = _viewResolver.FromViewModel(vm); // Spins up a view and sets its DataContext
reg.Add(vw);
reg.Activate(vw);
This all works fine, however I 'd really like to use the Prism navigation framework to do all this stuff for me so that I can do something like this:
_regionManager.RequestNavigate(
"MainRegion",
new Uri("NameOfMyViewModel", UriKind.Relative)
);
and have Prism spin up the ViewModel + View, set up the DataContext and insert the view into the region.
I 've had some success by creating DataTemplates referencing the ViewModel types, e.g.:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Module01">
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestView />
</DataTemplate>
</ResourceDictionary>
...and have the module add the relevant resource dictionary into the applications resources when the module is initialized, but that seems a bit rubbish.
Is there a way to effectively take over view creation from Prism, so that when RequestNavigate is called I can look at the supplied Uri and spin up the view / viewmodel based on that? There’s an overload of RegionManager.RegisterViewWithRegion that takes a delegate that allows you to supply a view yourself, and I guess I’m after something like that.
I think I might need to supply my own IRegionBehaviorFactory, but am unsure what's involved (or even if I am on the right path!).
Any help appreciated!
--
note: Originally posted over at the prism codeplex site
Sure you can do that. I 've found that Prism v4 is really extensible, if only you know where to plug in.
In this case, you want your own custom implementation of IRegionNavigationContentLoader.
Here's how to set things up in your bootstrapper (the example is from a subclass of UnityBootstrapper from one of my own projects):
protected override void ConfigureContainer()
{
// IMPORTANT: Due to the inner workings of UnityBootstrapper, accessing
// ServiceLocator.Current here will throw an exception!
// If you want access to IServiceLocator, resolve it from the container directly.
base.ConfigureContainer();
// Set up our own content loader, passing it a reference to the service locator
// (it will need this to resolve ViewModels from the container automatically)
this.Container.RegisterInstance<IRegionNavigationContentLoader>(
new ViewModelContentLoader(this.Container.Resolve<IServiceLocator>()));
}
The ViewModelContentLoader itself derives from RegionNavigationContentLoader to reuse code, and will look something like this:
public class ViewModelContentLoader : RegionNavigationContentLoader
{
private readonly IServiceLocator serviceLocator;
public ViewModelContentLoader(IServiceLocator serviceLocator)
: base(serviceLocator)
{
this.serviceLocator = serviceLocator;
}
// THIS IS CALLED WHEN A NEW VIEW NEEDS TO BE CREATED
// TO SATISFY A NAVIGATION REQUEST
protected override object CreateNewRegionItem(string candidateTargetContract)
{
// candidateTargetContract is e.g. "NameOfMyViewModel"
// Just a suggestion, plug in your own resolution code as you see fit
var viewModelType = this.GetTypeFromName(candidateTargetContract);
var viewModel = this.serviceLocator.GetInstance(viewModelType);
// get ref to viewResolver somehow -- perhaps from the container?
var view = _viewResolver.FromViewModel(vm);
return view;
}
// THIS IS CALLED TO DETERMINE IF THERE IS ANY EXISTING VIEW
// THAT CAN SATISFY A NAVIGATION REQUEST
protected override IEnumerable<object>
GetCandidatesFromRegion(IRegion region, string candidateNavigationContract)
{
if (region == null) {
throw new ArgumentNullException("region");
}
// Just a suggestion, plug in your own resolution code as you see fit
var viewModelType = this.GetTypeFromName(candidateNavigationContract);
return region.Views.Where(v =>
ViewHasDataContract((FrameworkElement)v, viewModelType) ||
string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) ||
string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
}
// USED IN MY IMPLEMENTATION OF GetCandidatesFromRegion
private static bool
ViewHasDataContract(FrameworkElement view, Type viewModelType)
{
var dataContextType = view.DataContext.GetType();
return viewModelType.IsInterface
? dataContextType.Implements(viewModelType)
: dataContextType == viewModelType
|| dataContextType.GetAncestors().Any(t => t == viewModelType);
}
// USED TO MAP STRINGS OF VIEWMODEL TYPE NAMES TO ACTUAL TYPES
private Type GetTypeFromName(string typeName)
{
// here you need to map the string type to a Type object, e.g.
// "NameOfMyViewModel" => typeof(NameOfMyViewModel)
return typeof(NameOfMyViewModel); // hardcoded for simplicity
}
}
To stop some confusion about "ViewModel first approach":
You use more a "controller approach", but no "ViewModel first approach". A "ViewModel first approach" is, when you inject your View in your ViewModel, but you wire up both, your ViewModel and View, through a third party component (a controller), what by the way is the (I dont want to say "best", but) most loosely coupled approach.
But to answer your Question:
A possible solution is to write an Extension for the Prism RegionManager that does exactly what you have described above:
public static class RegionManagerExtensions
{
public static void AddToRegion<TViewModel>(
this IRegionManager regionManager, string region)
{
var viewModel = ServiceLocator.Current.GetInstance<TViewModel>();
FrameworkElement view;
// Get View depending on your conventions
if (view == null) throw new NullReferenceException("View not found.");
view.DataContext = viewModel;
regionManager.AddToRegion(region, view);
regionManager.Regions[region].Activate(view);
}
}
then you can call this method like this:
regionManager.AddToRegion<IMyViewModel>("MyRegion");
I'm pretty new with Prism and after playing a bit around, there a few questions that arise. I'm trying to create a modular application that basically contains a map control in a shell window. The plugin modules offer different tools for interacting with the map. Some of the modules are pretty independent and simply display pins on the map.
1st question: How would RegionManager come into play for the module-specific classes (presenters) that must interact with the main map control? Usually in a RegionManager you register a specific view which is linked to a ViewModel, but in my case there is one single view (the map view) with multiple presenters acting on it.
2nd question: I need to be able to open several windows (shells) -- a bit like an MS Word document -- that should all be extended by the plugin modules. In a single-shell environment, when the module specific classes were instantiated, they could use the Dependency Injection Container to get a reference to the RegionManager or the Shell itself in order to get access to the map control. However with multiple shells, I don't see how to get access to the map control of the right shell. The dependency container has references to object global to the application, not specific for the shell I'm currently working in. Same is true for the EventAggregator.
Any input would be very welcome,
Ed
After hours of reading Prism-related articles and forums I've come across the article "How to build an outlook style application" on Erwin van der Valk's Blog - How to Build an Outlook Style Application.
In one part of the architecture, a Unity Child Container was used to resolve type instances. That's exactly what I needed for the answer to my 2nd question: I needed to have "scoped" (by window) dependency injection (ex: window scoped EventAggregator, Map control, etc.)
Here's how I create a new window:
private IShellWindow CreateNewShell(IRegionManager regionManager)
{
IUnityContainer childContainer = this.Container.CreateChildContainer();
... register types in child container ...
var window = new ShellWindow();
RegionManager.SetRegionManager(window, regionManager);
window.Content = childContainer.Resolve<MapDocumentView>();
return window;
}
So MapDocumentView and all its components will be injected (if needed) window-scoped instances.
Now that I can have scoped injected objects, I can get the window-scoped map in my module-based MapPresenter. To answer my 1st question, I defined an interface IHostApplication which is implemented by the Bootstrapper which has a MapPresenterRegistry property. This interface is added to the main container.
Upon initialization, the modules will register their presenters and upon the window creation, they will be instantiated.
So for the module initialization:
public void Initialize()
{
...
this.hostApplication.MapPresenterRegistry.Add(typeof(ModuleSpecificMapPresenter));
...
}
The code that initializes the map window:
private void View_Loaded(object sender, RoutedEventArgs e)
{
// Register map in the == scoped container ==
container.RegisterInstance<IMap>(this.View.Map);
// Create map presenters
var hostApplication = this.container.Resolve<IHostApplication>();
foreach (var mapPresenterType in hostApplication.MapPresenterRegistry)
{
var mapPresenter = this.container.Resolve(mapPresenterType) as IMapPresenter;
if (mapPresenter != null)
{
this.mapPresenters.Add(mapPresenter);
}
}
}
The module-specific MapPresenter:
public ModuleSpecificMapPresenter(IEventAggregator eventAggregator, IMap map)
{
this.eventAggregator = eventAggregator;
this.map = map;
this.eventAggregator.GetEvent<AWindowSpecificEvent>().Subscribe(this.WindowSpecificEventFired);
// Do stuff on with the map
}
So those are the big lines of my solution. What I don't really like is that I don't take advantage of region management this way. I pretty much have custom code to do the work.
If you have any further thoughts, I would be happy to hear them out.
Eduard
You have one main view and many child views, and child views can be added by different modules.
I'm not sure that the RegionManager class can be applied in this situation, so I would create a separate global class IPinsCollectionState
which must be registered as singleton in the bootstrapper.
public interface IPin
{
Point Coordinates { get; }
IPinView View { get; }
//You can use a view model or a data template instead of the view interface, but this example is the simplest
}
public interface IPinsCollectionState
{
ObservableCollection<IPin> Pins { get; }
}
Your main view model and different modules can receive this interface as a constructor parameter:
public class MapViewModel
{
public MapViewModel(IPinsCollectionState collectionState)
{
foreach (var item in collectionState.Pins)
{ /* Do something */ };
collectionState.Pins.CollectionChanged += (s, e) => {/* Handle added or removed items in the future */};
}
//...
}
Example of a module view model:
public class Module1ViewModel
{
public Module1ViewModel(IPinsCollectionState collectionState)
{
//somewhere in the code
collectionState.Pins.Add(new Module1Pin());
}
}
The second question can be solved in many different ways:
Application.Current.Windows
A global MainViewModel which contains the list of ShellViewModels and if you add new view model it will be displayed in new window. The bootstrapper is single for all windows.
Some kind of shared state which is passed to the constructor of the bootstrapper.
I don't know how these windows are related between themselves, and I don't know which way is the best, maybe it is possible to write an application with separated windows.