I am new to programming Swift and I have a Button with a Target to a function:
func buttonAction(sender:UIButton!)
{
self.window.rootViewController.presentViewController(FirstViewController(), animated: true, completion: nil);
}
Yet, I have a Button on the FirstView (of FirstViewController) and yet I want to go back to the MainView (of MainViewController) and I get the error Code:
2014-07-30 00:53:44.545 FifthTry[30275:833440] Warning: Attempt to present
<_TtC8FifthTry19FirstViewController: 0x787b5470> on <_TtC8FifthTry18MainViewController:
0x799802d0> whose view is not in the window hierarchy!
What is wrong?
It is great that you split your code into a separate View and Controller class. This is great class design. However, your controller is the one that should handle touch events according to Model-View-Controller which is the paradigm that Apple's frameworks use. The view is for displaying data and the controller is for handling user interaction on that view. If you setup the action on your controller, presenting a view controller is extremely easy because that is the way that Apple encourages you to break down your classes:
func buttonAction(sender:UIButton!)
{
self.presentViewController(FirstViewController(), animated: true, completion: nil)
}
Related
I created custom Poster view so it can be reused in multiple collection view cells (just like TVPosterView in TVUIKit). I add it directly to cell content view with all needed constraints.
The problem is when cell is focused this subview doesn't receive focus update (didUpdateFocus..) so I cannot customize it's focused / unfocused constraints etc. It's odd btw that image view inside is getting floating effect.
In case if I specify cell's preferredFocusEnvironments to return [self.posterView] + super. preferredFocusEnvironments, UI behaves as expected, but the collection view delegate method didSelect not called!
Thanks in advance for any help!
Seems didUpdateFocus not called on all subviews for the focused cell and it's system design. From docs:
After the focus is updated to a new view, the focus engine calls this
method on all focus environments that contain either the previously
focused view, the next focused view, or both, in ascending order. You
should override this method to update your app’s state in response to
changes in focus. Use the provided animation coordinator to animate
changes in visual appearance related to the update. For more
information on animation coordinators, see
UIFocusAnimationCoordinator.
Note: So it means didUpdateFocus will be called first on UICollectionViewCell, than on UIViewController subclasses, in ascending order. For subviews you need to manually register customDidUpdateFocus method that will be triggered in notification update. E.g. to update it's appearance we can use notifications (tvOS 11+), please see the example below.
func customDidUpdateFocus(isFocused: Bool, with coordinator: UIFocusAnimationCoordinator) { /* Custom logic to customize appearance */ }
// Register observer
observerToken = NotificationCenter.default.addObserver(forName: UIFocusSystem.didUpdateNotification, object: nil, queue: .main) { [weak self] (note) in
guard let self = self else { return }
guard let context = note.userInfo?[UIFocusSystem.focusUpdateContextUserInfoKey] as? UIFocusUpdateContext else { return }
guard let coordinator = note.userInfo?[UIFocusSystem.animationCoordinatorUserInfoKey] as? UIFocusAnimationCoordinator else { return }
if let prev = context.previouslyFocusedView, self.isDescendant(of: prev) {
self.didUpdateFocus(isFocused: false, with: coordinator)
} else if let next = context.nextFocusedView, self.isDescendant(of: next) {
self.didUpdateFocus(isFocused: true, with: coordinator)
}
}
My UISplitViewController basically works like a charm except that there is an annoying error message displayed when transitioning the first time (first time only!) from the master table view to the detail view.
Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x160015600>.
Both the master and the detail view controller are embedded in a UINavigationController. However, the error only occurs when setting the following (which is necessary for logic behavior on the iPhone):
class MySplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
return true
}
}
It would be great if anyone could provide a solution to this issue, thanks in advance.
BTW: the split view controller was set up in the storyboard
Presenting the detail view controller is done in the tableView:didSelectRowAtIndexPath: method like this:
if let detailViewController = delegate as? DetailViewController {
detailViewController.navigationItem.leftItemsSupplementBackButton = true
detailViewController.navigationItem.leftBarButtonItem = splitViewController!.displayModeButtonItem()
splitViewController!.showDetailViewController(detailViewController.navigationController!, sender: self)
}
Most probably, your first transition from master (UITableView in UIViewController?) to detail (UIViewController) view in your UISplitViewController starts before the active/current view has finished displaying itself.
A possible reason for this is that you are possibly trying to present the first "instance" of the detail view in the viewDidLoad() method of you master UIViewController? In such a case, you app might try to present the detail view prior to master view finished appearing. Note the difference here between view did load a view and view did appear:
override func viewDidLoad()
Description:
Called after the controller's view is loaded into memory.
This method is called after the view controller has loaded its view
hierarchy into memory.
override func viewDidAppear(animated: Bool)
Description:
Notifies the view controller that its view was added to a view
hierarchy. You can override this method to perform additional tasks
associated with presenting the view.
Now, as you question doesn't show how you load your initial detail view, the following advice is maybe already heeded by yourself, but anyway: if your detail view is presented from the viewDidLoad(), try to move this to the viewDidAppear() method:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
// present/load detail view here instead
}
This might be too late an answer, but anyways, I solved this using perform segue instead of showDetailViewController
I recently started using the Durandal library for a SPA I am developing... kudos to the author, it is an excellent library.
I like the concept of widgets, vs using Views for stateless screens, but I was not able to display a widget in a modal dialog, without attaching it to a view. Does anyone know how to do this?
To elaborate, there is a widget.create function that allows for the creation of a widget in the JS but requires a DOM element to attach to. What I would prefer to do is create a widget, without attaching it to the DOM, then call something like:
app.showModal(theWidget);
As an alternative, I know I can create a "dialog" view that maps to swappable widgets, then use that view for dialogs, e.g. the view would have:
<div data-bind="widget: {kind:widgetId}">/div>
... and then:
app.showModal('viewmodels/dialog');
where 'viewmodels/dialog.js' is the view-model for the "Dialog" view.
References:
Modals: http://durandaljs.com/documentation/Showing-Message-Boxes-And-Modals/
Widgets: http://durandaljs.com/documentation/Creating-A-Widget/
Widgets are meant to be reusable controls on a web page, so that's why they require a DOM element. I'm not sure if I completely understand what you're trying to do, but you can define a view that returns its constructor function rather than a singleton object.
Here's a view that returns a singleton:
define([], function() {
var singleton = {
title: "I'm Mr. Singleton"
};
return singleton;
});
Here's the same view, but returns its constructor function:
define([], function() {
var notSingleton = function () {
this.title = "I'm NOT Mr. Singleton"
};
return notSingleton;
});
You can then use either of these within another viewmodel or module, as such:
define(['viewmodels/singleton', 'viewmodels/notSingleton'],
function(singleton, NotSingleton) {
...
app.showModal(singelton);
app.showModal(new NotSingleton());
...
});
In the latter case, you could create multiple dialogs of the same viewmodel across multiple other views, but each would be its own instance with its own properties. If you wanted to share data and/or behaviors across all instances of that viewmodel type, you could add them to the viewmodel's prototype.
Hope this helps.
I have a parent view and a modal view with a text box. What I am trying to do is pass whatever is entered into the text box from the modal view and then pass it to a label in the parent view which updates the label to what was entered. I hope that made any sense.
I have been pulling my hair out for a couple of weeks trying to figure this out with no luck. I found many examples and tutorials about segues and passing between views that are being pushed but nothing about modal views and passing back to the parent view.
I have been trying to understand this and need a good example. I kind of understand the prepare for segue concept but for some reason, I just can't figure this one out. Any help on this would be much appreciated and you would be my hero for life lol.
In my project that uses segues, here's how I did it (note that I'm new to iOS, so there's probably "better" ways, and this may be obvious to the iOS veterans):
The short version: define a callback protocol in your modal view controller's .h file. When your modal view controller closes, it checks to see if the presenter implements that protocol and invokes those methods to pass along the data.
So like you said, let's say your modal view controller just gathers a single string value from the user and then they click OK or Cancel. That class might look like this:
#interface MyModalViewController : UIViewController
...
#end
I'm suggesting you add a protocol like this to the same header:
#protocol MyModalViewControllerCallback
-(void) userCancelledMyModalViewController:(MyModalViewController*)vc;
-(void) userAcceptedMyModalViewController:(MyModalViewController*)vc
withInput:(NSString*)s;
#end
Then in MyModalViewController.m, you add a viewDidDisappear with code similar to this:
-(void) viewDidDisappear:(BOOL)animated {
UIViewController* presenter = self.presentingViewController;
// If the presenter is a UINavigationController then we assume that we're
// notifying whichever UIViewController is on the top of the stack.
if ([presenter isKindOfClass:[UINavigationController class]]) {
presenter = [(UINavigationController*)presenter topViewController];
}
if ([presenter conformsToProtocol:#protocol(MyModalViewControllerCallback)]) {
// Assumes the presence of an "accepted" ivar that knows whether they
// accepted or cancelled, and a "data" ivar that has the data that the
// user entered.
if (accepted) {
[presenter userAcceptedMyModalViewController:self withInput:data];
}
else {
[presenter userCancelledMyModalViewController:self];
}
}
[super viewDidDisappear:animated];
}
And finally in the parent view, you implement the new #protocol, e.g. in the .h:
#interface MyParentViewController : UIViewController <MyModalViewControllerCallback>
...
#end
and in the .m:
#implementation MyParentViewController
...
-(void) userCancelledMyModalViewController:(MyModalViewController*)vc {
// Update the text field with something like "They clicked cancel!"
}
-(void) userAcceptedMyModalViewController:(MyModalViewController*)vc
withInput:(NSString*)s {
// Update the text field with the value in s
}
...
#end
Having tried a number of different solutions I keep coming back to this. I need a Window.ShowDialog, using the ViewModelLocator class as a factory via a UnityContainer.
Basically I have a View(and ViewModel) which on a button press on the the view needs to create a dialog (taking a couple of parameters in its constructor) that will process some logic and eventally return a result to the caller (along with the results of all the logic it computed).
Maybe I'm wrong for stilll looking at this from a Windows Forms perspective, but I know exactly what I want to do and I want to ideally do it using WPF and MVVM. I'm trying to make this work for a project, and ultimately don't want to have to go back to vanilla WPF in order to make it work.
I break the rules to implement a dialogwindow but tried to reduce it to a minimum. I have a method OpenDialog in my BaseViewModel:
public void OpenDialog(DialogViewModel model)
{
this.MessengerInstance.Send<DialogViewModel, MainWindow>(model);
}
And in my MainWindow:
Messenger.Default.Register<DialogViewModel>(this, model =>
{
// Instantiate the dialog box
var dlg = new DialogWindow();
// Configure the dialog box
dlg.Owner = this;
dlg.Content = model;
// Open the dialog box modally
dlg.ShowDialog();
});
That way i only have a loose coupling between my viewmodel and my MainView.
You can do the same for closing, my BaseDialogViewModel has a method:
public void CloseDialog()
{
this.MessengerInstance.Send<PopUpAction, DialogWindow>(PopUpAction.Close);
}
(PopupAction is just an enum) and my DialogWindow registers for that:
Messenger.Default.Register<PopUpAction>(this, action =>
{
switch (action)
{
case PopUpAction.Close:
this.Close();
break;
}
});
You could also leave the receiver away when sending, to keep the view class out of the viewmodel but either way i think it's a acceptable solution :)
You can do that. Just create an instance of a page/usercontrol/window and call instance.ShowDialog().
Here's my T4 templates to generate a view/viewmodel with the messaging for closing a window and other tricks.