navigationController!.pushViewController vs. presentViewController in iOS Swift - uinavigationcontroller

What are the implications of pushing a ViewController in UINavigation vs. presenting a ViewController modally in terms of changing values in the next view?
For example, why does the first work but not the second?
First:
var textController: TextViewController
textController = self.storyboard!.instantiateViewControllerWithIdentifier("TextViewController") as! TextViewController
presentViewController(textController, animated: false, completion: nil)
textController.textDetail.text = Categories[indexPath.row]
Second:
var textController: TextViewController
textController = self.storyboard!.instantiateViewControllerWithIdentifier("TextViewController") as! TextViewController
self.navigationController!.pushViewController(textController,animated:true)
textController.textDetail.text = Categories[indexPath.row]
I can't get the label's value to change when pushing in a navigation stack.

It appears as though when calling the presentViewController method, the view of the view controller is actually loaded during the call, whereas when calling the pushViewController on the navigation controller, the view itself is loaded after the call.
You can test this yourself by printing to the console before and after presenting/pushing the view controller, and printing to the console in the TextViewController's viewDidLoad method.
The view needs to be loaded for the textDetail variable to load (you haven't said so, but I'm assuming this is an IBOutlet) and the textDetail variable needs to load for you to be able to modify its text property.
To use pushViewController you could for example set a variable on your TextViewController class, and override its viewDidLoad method, where you could then set the text property on the textDetail variable.

Related

Kotlin - Trying to populate spinner from Fragment using findViewById(), but context: this is throwing an error

Obligatory preface: I'm quite new to Kotlin and Android Studio. As the title states, I'm trying to populate a spinner in Android Studio from within a fragment. First, I had an issue with findViewById(R.id.spinner) but I believe I've resolved it by prefixing it with root..
Currently, the only error being thrown is the context: this line. Ultimately, I'd like to use this spinner to allow the user to filter by different NY boroughs (hence, boroughs_array. Here's my current code within the FilterFragment -- my attempt to populate the spinner begins below return root.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
filtersViewModel =
ViewModelProviders.of(this).get(FiltersViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_filters, container, false)
return root
val spinner: Spinner = root.findViewById(R.id.spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter.createFromResource(
this,
R.array.boroughs_array,
android.R.layout.simple_spinner_item
).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
spinner.adapter = adapter
}
My current assumption is that this is not the right context, as I'm within a fragment. On the off-chance that this is correct, I'm not really sure how to handle this. If you can shine any sort of light on the issue, I will be eternally grateful.
You need to use use context!! instead of this. this refers to the current Fragment, which is not a Context. The current Fragment has a reference to the Context though, accessed via this.getContext(), or context for short.
The reason you need context!! is because getContext() is nullable (it may return null). It's safe in this case to 'force unwrap' (!!) because context will never be null inside onCreateView().
Another issue I've spotted, is you're returning from the onCreateView() function before you've set up the spinner.
Try this instead:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
filtersViewModel = ViewModelProviders.of(this).get(FiltersViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_filters, container, false)
val spinner: Spinner = root.findViewById(R.id.spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter.createFromResource(
context!!,
R.array.boroughs_array,
android.R.layout.simple_spinner_item
).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
spinner.adapter = adapter
return root
}
Also, just for further clarification - you've probably seen some examples where this is passed in for context. This is often done inside of an Activity, since Activity does extend Context.
You can access context like this:
context?.let { context ->
ArrayAdapter.createFromResource(
context,
R.array.boroughs_array,
android.R.layout.simple_spinner_item
).also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
}
}

Creating a Xamarin.Forms.DataTemplate from a view function

A CollectionView (or ListView) provides a way to view a sequence of objects (of type T). It has the advantage, compared to a StackLayout or Grid, that views are only loaded as needed.
The natural way to specify the view for each object would be to provide a function v:T->View (F#).
However CollectionView/ListView expects a DataTemplate. The DataTemplate class is very tied to bindings and as such the API is unnatural, uninformative and type-unsafe. The useful property seems to be Values with type IDictionary<BindableProperty,Object>.
Is it possible to work around this API and make a function which takes a v:T->View and returns a DataTemplate? This would allow making a clean API for DataTemplate and therefore for ListView and CollectionView.
One way to achieve this would be to create your own DataTemplate and ViewCell.
DataTemplate only requires a type deriving from ViewCell to instantiate when needed by the ListView.
It then calls the OnBindingContextChanged method of this newly created/reused ViewCell and passes the corresponding value of the element.
At this moment, you have access to the ViewCell.View property which will contain the control you want to display.
You can execute your function at that time.
In your case, since you have a list of 'T, this would be something like this:
type FuncViewCell(createFunc: 'T -> View) =
inherit ViewCell()
override x.OnBindingContextChanged () =
let data = x.BindingContext :?> 'T
x.View <- createFunc data
type FuncDataTemplate(createFunc: 'T -> View) =
inherit DataTemplate(fun () -> FuncViewCell(createFunc))
(...)
let createViewForData data =
Button(Text = data.Text)
let listView = ListView()
listView.ItemTemplate <- FuncDataTemplate(createViewForData)
listView.ItemsSource <- dataSource
----
Or even directly:
type FuncListView(createFunc) =
inherit ListView(DataTemplate = FuncDataTemplate(createFunc))
let listView = FuncListView(createViewForData)
listView.ItemsSource <- dataSource
A similar approach can be found in Fabulous.XamarinForms.
The difference is that the element comes with its own view creation function. So there is no need to extend DataTemplate to pass a function.
https://github.com/fsprojects/Fabulous/blob/79c5df748fff7a108dfbcbf8609cb2265a8fddc7/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/CustomControls.fs#L110-L151
the solution would be to use DataTemplateSelector.
Here is the link for this:-
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector
What you can do is get a DataTemplate Id from an API call and based on the Id you can select that particular DataTemplate. You will have make that many DataTemplates and store in the Xaml of the Page. This would be the most safest and easiest solution.

Using CEFSharp ILifeSpanHandler interface to Handle Popups

I have an issue with handling popups. I have implemented ILifeSpanHandler and OnBeforeBrowse (amoungst others) from the IRequestHandler.
How do I know in the ILifeSpanHandler what URL is being called? I am unable to get it in either the OnAfterCreated or OnBeforePopup. Currently I see it first in OnBeforeBrowse.
I have no code as my question is a "How to". In OnBeforePopup I have checked targetUrl however it seems to be there for decoration as I have read that it is not implemented anyway. I have also looked at the browner/chromiumWebBrowser objects, browser and newBroswer seem to be nothing. One would expect in OnAfterCreated chromiumWebBrowser would return an object but it is nothing in my case.
I am testing with the following
Public Sub OnAfterCreated(chromiumWebBrowser As IWebBrowser, browser As IBrowser) Implements ILifeSpanHandler.OnAfterCreated
Try
Debug.Print(vbNewLine)
Debug.Print("OnAfterCreated")
Debug.Print(String.Concat("OnAfterCreated - MainFrame.Url "), browser.MainFrame.Url)
Debug.Print("OnAfterCreated")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
End Sub
And I have the following
Public Function OnBeforePopup(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetFrameName As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean, popupFeatures As IPopupFeatures, windowInfo As IWindowInfo, browserSettings As IBrowserSettings, ByRef noJavascriptAccess As Boolean, ByRef newBrowser As IWebBrowser) As Boolean Implements ILifeSpanHandler.OnBeforePopup
Try
Debug.Print(vbNewLine)
Debug.Print("OnBeforePopup")
Debug.Print(String.Concat("OnBeforePopup - targetUrl "), targetUrl)
Debug.Print(String.Concat("OnBeforePopup - browser.MainFrame.Url "), browser.MainFrame.Url)
Debug.Print(String.Concat("OnBeforePopup - chromiumWebBrowser.Address "), chromiumWebBrowser.Address)
Debug.Print("OnBeforePopup")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
Return False
End Function
I have seen different approaches in handling popups using ILifeSpanHandler interface. One approach that I've seen also here in Stack Overflow and was accepted as the correct answer to that particular question is to return true in the OnBeforePopup implementation of ILifeSpanHandler then pass the targetURL argument to a handler that creates the popup.
This approach is very unideal because you are destroying the link between the page that actually opened the popup and the popup itself. If you access via JavaScript the opener property of the window object inside the popup you would notice that it is null. And the page that opened the popup would never know that the popup was actually opened because returning true cancels the creation.
The other approach is to let Cef create the popup and the programmer just decides whether to show the browser as a popup window or a child to control (like in tabbed browsing). This is error-free and almost ideal. But the problem with this approach is that you are not able to listen to events such as FrameLoadStart, FrameLoadEnd, AddressChanged, TitleChanged, etc.
One approach that is tagged experimental by the Cef developers is to return a new IWebBrowser instance via newWebBrowser out parameter. This has so many many side effects. The page that opened the popup would, of course, recognize the popup as his although it was not the original browser (IBrowser) that it created. The page may just ignore it like btcclicks.com and in that case, there'd be no problem. But there are websites like drops.xyz that is so particular with his stuff and will discard everything that is not originally his. So this is a problem.
So what is the correct approach?
The ChromeWebBrowser control
Now I'm going to share with you an undocumented approach in handling popups. Speaking of ChromeWebBrowser control, it is very much of help that we know how it creates the webbrowser which, in reality, it doesn't. The control just hosts the webbrowser window handle. It has a private field called managedCefBrowserAdapter (ManagedCefBrowserAdapter class) that handles the actual creation of the web browser. ChromiumWEbBrowser implements the IWebBrowserInternal that has a method OnAfterBrowserCreated with a single parameter of type IBrowser. The control then invokes browser.GetHost().GetWindowHandle() to get the actual window handle (HWND) of the webbrowser it is being hosted. It is quite good.
The problem of the ChromeWebBrowser is that it won't have a constructor that accepts an IBrowser as an argument. It only has constructor that accepts HtmlString, string and IRequestContext arguments. These control waits for the
invocation of OnHandleCreated (a base class override) where it calls the managedCefBrowserAdapter.CreateBrowser after which it waits till its implementation of IWebBrowserInternal's OnAfterBrowserCreated is invoked.
Again, what is the approach that works?
Now, this approach that actually works is a product of long series of trial and error. One caution though is that I don't know why and how it works but I know it works.
First, I did not use ChromeWebBrowser. But I copied its code omitting the part where it creates .net control. In this case, I am targeting the browser's window handle (HWND) to be host by any object that exposes a HWND. Obviously I created a class (NativeCefWebBrowser) that uses the modified code. The ChromeWebBrowser orignal constructors were still there untouched becuase they are used to the create the parent webrowser. But I added one constructor that accept the following arguments: ICefBrowserParent parent (an interface I've created and IBrowser browser that receives the browser argument in the ILifeSpanHandler's OnBeforePopup. I also added a public method AttachBrowser that has a single parameter IBrowser that recieves the IBrowser argument in the ILifeSpanHandler's OnAfterCreated. It the browser that will be kept by CefNativeWebBrowser class.
Why didn't I keep the browser instance received form ILifeSpanHandler.OnBeforePopup but used the instance received from ILifeSpanHandler.OnAfterCreated when they are the same browser instance? This is one of those parts that I don't know why. One thing I noticed is that when I called browser.GetHost().GetWindowHandle() during ILiffeSpanHandler.OnBeforePopup, the first window handle I received was the different compared to when I invoked the method during ILifeSpanHandler.OnAfterCreatd. Because of that, I store the browser instance from the latter that I passed to the NativeCefWebBrowser.AttachBrowser for its safekeeping.
In the NativeCefWebBrowser(ICefBrowserParent parent, IBrowser browser) contructor, I set the private following fields to true: browsercreated, browserinitialized (chromewebbrwoser orginal fields) and isAttachingBrowser (added private field). You don't call the ManagedCefBrowserAdapter's CreateBrowser in this contructor in instead call its OnAfterBrowserCreated passing the browser instance. You don't much in this constructor as you will wait the ILifeSpanHandler implementor to pass you the browser instance it will receive during its OnAfterCreated method. Take note that when calling the ManagedCefBrowserAdapter's OnAfterBrowserCreated method, ManagedCefBrowserAdapter will still invoke IWebBrowserInternal implementation of OnAfterBrowserCreated that when happens you have to exit immediately when isAttachingBrowser is true as the following code will no sense no more.
After calling the NativeCefWebBrowser(ICefBrowserParent, IBroser) construct, you can normally set event listeners as you will normally do.
And that's it.
The following are parts of the code that I wrote
The ICefBrowserParent interface
public interface ICefBrowserParent
{
IntPtr Handle { get; }
Size ClientSize { get; }
bool Disposing { get; }
bool IsDisposed { get; }
bool InvokeRequired { get; }
IAsyncResult BeginInvoke(Delegate d);
object Invoke(Delegate d);
event EventHandler Resize;
}
As you would notice, the methods, properties and events in this interface are already implemented by the System.Windowns.Forms.Control class. So if you implementing this from class inhering Control class, you would not need to implement this anymore. This interface is only for non-Control class.
class NativeCefWebBrowser
{
public NativeCefWebBrowser(ICefBrowserParent, IBroser)
{
requestContext = browser.GetHost().RequestContext;
this.parent = parent; // added field
HasParent = true; // IWebBrowserInternal. I don't know what's this for
mustSetBounds = true; // added field
browserCreated = true;
isAttachingBrowser = true; // added field
InitializeFieldsAndCefIfRequired();
managedCefBrowserAdapter.OnAfterBrowserCreated(browser);
}
}
ILifeSpanHandler.OnBeforePopup(..., out IWebBrowser newWebBrowser)
{
CefNativeWebBrowser b = new CefNativeWebBrowser
(
parent, // defined else where
browser
);
// Attach event handlers
b.TitleChanged...;
newWebBrowser = b;
}
ILifeSpanHandler.OnAfterCreated(...)
{
((CefNativeWebBrowser)webBrowser).AttachBrowser(browser);
}

How to trigger an action from a NSTableCellView in view based NSTableView when using bindings

I'm facing a problem with a view-based NSTableView running on 10.8 (target is 10.7, but I think this is not relevant).
I'm using an NSTableView, and I get content values for my custom NSTableCellView through bindings. I use the obejctValue of the NSTableCellView to get my data.
I added a button to my cell, and I'd like it to trigger some action when clicked. So far I have only been able to trigger an action within the custom NSTableCellView's subclass.
I can get the row that was clicked like this, using the chain:
NSButton *myButton = (NSButton*)sender;
NSTableView *myView = (NSTableView*)myButton.superview.superview.superview;
NSInteger rowClicked = [myView rowForView:myButton.superview];
From there I don't know how to reach my App Delegate or controller where the action is defined.
As I am using cocoa bindings, I do not have a delegate on the NSTableView that I could use to trigger my action.
Do you have any idea how I could talked back to controller ?
Many thanks in advance!
Although you are using bindings you can still set your controller as the delegate for your tableview in the interface builder.
I see that you already are able to access the table view from inside your cell. The next task must be simple, just set the table view delegate as the target for your button's action.
Thanks for your question, I also will be triggering an action from a button on a NSTableView. Your question helped to put me on the correct path.
First to address the your solution to finding which row number my NSTableView is on. I was able to find it without knowing the button, in my custom NSTableView I installed the following as a first attempt:
- (NSInteger)myRowNumber
{
return [(NSTableView*)self.superview.superview rowForView:self];
}
this works fine, however it is less than robust. It only works if you already know specifically how deep you are in the view hierarchy. A more robust and universal solution is:
- (NSInteger)myRowNumber
{
NSTableView* tableView = nil;
NSView* mySuperview = self;
do
{
NSView* nextSuper = mySuperview.superview;
if (nextSuper == nil)
{
NSException *exception =
[NSException exceptionWithName:#"NSTableView not found."
reason:[NSString stringWithFormat:#"%# search went too deep.",
NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
}
if ([nextSuper isKindOfClass:[NSTableView class]])
tableView = (NSTableView*)nextSuper;
else
mySuperview = mySuperview.superview;
} while (tableView == nil);
return [tableView rowForView:self];
}
this not only works at the NSTableView level, but works with anything installed at any level above it, no matter how complex the view hierarchy.
As to the unanswered part of your question, I established an IBOutlet in my class and using interface builder tied if to my files owner (in my case my document class). Once I had a reference to the class I was sending my message to, and the row number, I call the function. In my case the call required that I pass the row number it originates from.
[self.myDoc doSomethingToRow:self.myRowNumber];
I tested this and it works at various levels of the view hierarchy above NSTableView. And it functions without having to have the row selected first (which appears to be assumed in Apples documentation).
Regards, George Lawrence Storm, Maltby, Washington, USA
Use rowForView: and the responder chain
To respond to a control's action embedded within an NSTableCellView, the control should issue the action to the First Responder. Alternatively, File Owner is possible but this is more tightly coupled.
Use rowForView: within the action method to determine which row's control issued the action:
- (IBAction)revealInFinder:(id)sender {
NSInteger row = [self.tableView rowForView:sender];
...
}
The action is implemented within any of the responder chain classes. Most likely, this will be your subclassed NSWindowController instance. The responder could also be the application delegate; assuming the delegate has a means to talk to the NSTableView.
See Apple's example TableViewPlayground: Using View-Based NSTableView and NSOutlineView to see this in action.
Suhas answer helped me.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "EDIT_CELL_VIEW"), owner: self) as? SymbolManagerCell {
if let editButton = cell.subviews[0] as? NSButton {
editButton.target = cell // this is required to trigger action
}
return cell
}
return nil
}

Null cast between parent and child objects

I have a flex application with two objects: a parent widget (called an IBaseWidget) and a child widget (called a HelperWidget2). When the user clicks on a help link, a helper widget is loaded into the list of base widgets and then displayed for the user.
However, when I try to access this child widget by casting the base widget in the collection to the child widget type, the child widget returns null and I am unable to work with the widget.
The following snippet correctly returns the widget ID of the newly added widget and dispatches an event to load the widget:
var id:Number = WidgetManager.getInstance().getWidgetId("Helper");
ViewerContainer.dispatchEvent(new AppEvent(AppEvent.WIDGET_RUN, id, openQuickQueryCanvas));
Once the widget is loaded, a callback function called openQuickQueryCanvas() attempts to do another action with the helper widget:
private function openQuickQueryCanvas():void{
var id:Number = WidgetManager.getInstance().getWidgetId("Helper");
var bWidget:IBaseWidget = WidgetManager.getInstance().getWidget(id) as IBaseWidget;
var helperWidget:HelperWidget2 = bWidget as HelperWidget2;
if(helperWidget != null){
helperWidget.quickQueryCanvas.dispatchEvent(new MouseEvent(MouseEvent.CLICK));//fire an event to open the quick query canvas
}
}
The problem is that helperWidget above always returns null, meaning the cast isn't successful. This doesn't make sense to me, because bWidget is of type HelperWidget2.
Any thoughts? I'm stumped...
First off, make sure that HelperWidget2 implements IBaseWidget like so
public class HelperWidget2 implements IBaseWidget
Second, I would suggest using the is keyword instead of casting and checking for null:
private function openQuickQueryCanvas():void {
var id:Number = WidgetManager.getInstance().getWidgetId("Helper");
var bWidget:IBaseWidget = WidgetManager.getInstance().getWidget(id) as IBaseWidget;
if(bWidget is HelperWidget2)
{
HelperWidget2(bWidget).doWhatever();
}
}
Cast the returning instance as an object, instead of HelperWidget2. You won't have intellisense for the methods at design time, but more importantly, it won't be null at run time.
var bWidget:Object = WidgetManager.getInstance().getWidget(id);
bWidget.doWhatever();

Resources