MvvmCross: Programmatically adding a view to CollectionView's subview - iphone

Depending on user action, I want to dynamically add a view (within MvxViewController) to the subviews collection of a CollectionView. Here's what I'm doing:
Does NOT work: SubviewThree inherits MvxViewController
var mvxViewController = new SubviewThree(); // SubviewThree inherits MvxViewController
var uiView = mvxViewController.View;
uiView.ClipsToBounds = true;
uiView.Hidden = false;
uiView.Frame = new RectangleF(0, 0, 320, 200);
CollectionView.AddSubview(uiView);
CollectionView.BringSubviewToFront(uiView);
Works: SubviewThree inherits UIViewController
var mvxViewController = new SubviewThree(); // SubviewThree inherits UIViewController
var uiView = mvxViewController.View;
uiView.ClipsToBounds = true;
uiView.Hidden = false;
uiView.Frame = new RectangleF(0, 0, 320, 200);
CollectionView.AddSubview(uiView);
CollectionView.BringSubviewToFront(uiView);
Works:
var label = new UILabel(); // All simple controls work just fine
label.Text = "Test Label";
label.ClipsToBounds = true;
label.Hidden = false;
label.Frame = new RectangleF(0, 0, 320, 200);
CollectionView.AddSubview(label);
CollectionView.BringSubviewToFront(label);
What's the best way to achieve this in MvvmCross? Note that I cannot use data binding (and add it via view model's ObservableCollection) here since the view to be added depends on user action - it may not be of same type.
Here's what I'm trying to do:
On right/left button touch, I want to swap views. The views are bound to different view models. The swapped view can either be either of V1 type (having a bar and another UIView) or some other type (just a UIView).
Here's my github link with sample project, just in case if the above image is not clear. You can just run the app and click on Add button. The code for adding view to subview is in BarCell.cs, line 158. There I've commented the above mentioned lines of code.
Github Link

In order for MvvmCross to create a View and to hook it up with it's ViewModel, MvvmCross uses a MvxViewModelRequest object. In iOS this is connected to the View using a Request property.
You can hook this up yourself if you want to - it's just a get/set property that has to be set before ViewDidLoad occurs - see IMvxTouchView.cs
Alternatively:
if you want MvvmCross to create the View (including doing the ViewModel-View lookup) then there are some extension methods in https://github.com/slodge/MvvmCross/blob/v3/Cirrious/Cirrious.MvvmCross.Touch/Views/MvxCanCreateTouchViewExtensionMethods.cs
if you don't need a viewController, then the MvxView class may be sufficient for you - see the N=32 video for an example of its use - http://slodge.blogspot.co.uk/2013/06/n32-truth-about-viewmodels-starring.html

Related

Exist way notify when custom UIView will be presented or removeFromSuperview()?

I am create custom UIView using this code:
lazy var temporary: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .white
return view
}()
Exist way use NotificationCenter notify when this view will be presented and removed from VC if I am use:
self.view.addSubview(temporary)
or
temporary.removeFromSuperview()
I'm not totally sure what you are asking. I think you're asking for a way to tell when your custom view is added as a subview of another view.
The easiest way to do that is to make your view a custom subclass of UIView, and implement didMoveToSuperview() or willMove(toSuperview:). Those methods get called when your view is added as a child view of another view.
If you really want to use the notification center you could have your custom view class broadcast a notification when it gets added to a superview.

How to access storyboard items without #IBOutlets?

I am currently working in a IOS app where I have created a storyboard and some viewControllers.
Because many of the areas are similar I choose to create a generic viewController and then populate it with a controller that extends UIViewController.
Since I cannot add two different classes to a viewController in interface builder, I cannot link the viewController to two different controllers.
What would be the best way to accomplish such task?
Having a controller linked with the storyboard viewController and this controller handles every view that needs to be instantiated?
You can always use tags in Interface Builder for your controls.
For instance, let say you set a label with a tag of 100, and a button with a tag of 110.
var label : UILabel! { view.viewWithTag(100) as? UILabel }
var button : UIButton! { view.viewWithTag(110) as? UIButton }
So now, you can do things like:
label.text = "Yeppee!"

Swift - Accessing child label text from parent after addSubview

Hi have an app that adds multiple subviews (UIViews) to the parent using:
if let page:UIViewController = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
scrollView.addSubview(page.view)
}
In RecentViewController I have a number of elements (labels, image views etc). I would like to set these from the parent view controller. Is this possible and if so any resources?
I have been searching for a while but I can only find info when segues are used.
Thanks
Since you are using storyboard you can create referencing outlets for views in RecentViewController by dragging them from storyboard to view controller class. Then you can access these properties directly as follow :
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
page.someChild.image = someImage // raw example
self.recentViewController = page
scrollView.addSubview(page.view)
}
You have most the work done for you, create a property in the parent view controller to keep the child view controller.
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
self.recentViewController = page
scrollView.addSubview(page.view)
}
Now you can access anything in the child view controller with self.recentViewController.<childControl>.
IMPORTANT NOTE: When setting up this kind of parent-child relationships, you should use the method from Creating Custom Container View Controllers
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
self.recentViewController = page
self.addChildViewController(page)
page.view.frame = self.frame
scrollView.addSubview(page.view)
page.didMoveToParentViewController(self)
}
I know this looks mysterious and unnecessary but it will prevent odd nearly untraceable issues in the future.
Oh, and if you ever need to remove the child view
self.recentViewController.willMoveToParentViewController(nil)
self.recentViewController.view.removeFromSuperview()
self.recentViewController.removeFromParentViewController()

Add view over tableview (UITableViewController)

Situation: I've got a UITableViewController loading some data asynchronously from a service. During this time I would like to place a full screen (except navigation bar) view over the table view showing my custom indicator and text.
Problem: The problem I'm facing is that when my custom view (it has a red background) is placed over the UITableView the lines of the table view are shown trough my custom view (see image below).
What I tried:
I tried to use insertBelow and above, didn't work. I also tried to do: tableview.Hidden = true, but this also hides the custom view for some reason as seen on image 2.
Image1: For some reason I can see the lines threw my view.
Image 2: Tableview + custom view gone when hidden = true used.
My code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
UIView view = new UIView (new RectangleF (0, 0, this.TableView.Frame.Width, this.TableView.Frame.Height));
view.BackgroundColor = UIColor.Red;
this.TableView.AddSubview (view);
TableView.Source = new SessionTableViewSource ();
}
You can use self.navigationController.view as view for adding subview.
The issue is that the View of a UITableViewController is a UITableView, so you cannot add subviews to the controller on top of the table.
I'd recommend switching from a UITableViewController to a simple UIViewController that contains a UITableView. This way the controller main view is a plain UIView that contains a table, and you can add subviews to the main UIView and they will be placed on top of the table view.
You can try to add the view to the window instead of nesting it in the table view like this:
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: overlayview];
UIWindow* window = [[UIApplication sharedApplication].delegate.window;
[window addSubview: your-overlayview];
Swift / Storyboard Solution
Note: The code below assumes one has a custom view (ratingView in my case) that is to be presented over a UITableView.
I've read many answers to this and similar questions on SO. The other answers from these sources worked to varying degrees for me (e.g.,view loaded but not shown or not accessible,...). I am using Swift 2.0+ and I am sharing the complete solution for doing this using a UITableViewController.
Create an outlet to the Navigation Bar and the view, which you want to bring over the tableview.
//MARK:Outlets
#IBOutlet weak var navBar:UINavigationBar!
#IBOutlet var ratingView: MNGStarRating!
In my case I also wanted to animate the view over the tableview so I used a class variable to hold a reference to the inflection point and a point above the scene (off-screen).
var centerYInflection:NSLayoutConstraint!
var aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)
Then in viewDidLoad I called a function (configureRatingViewAutoLayout) which configures and adds the constraints for the new view to be animated over the tableview.
func configureRatingViewAutoLayout() {
//REQUIRED
self.navBar.superview?.addSubview(self.ratingView)
var newConstraints:[NSLayoutConstraint] = []
newConstraints.append(self.ratingView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor,constant: 10))
newConstraints.append(self.ratingView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor,constant: 10))
newConstraints.append(self.ratingView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor))
//hides the rating view above the scene
self.centerYInflection = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor, constant: self.aPointAboveScene)
//the priority must be set below 1000 if you intend to change it after it has been added to a view
self.centerYInflection.priority = 750
newConstraints.append(self.centerYInflection)
//constraints must be added to the container view of the two items
self.ratingView.superview?.addConstraints(newConstraints)
}
Nota Bene - On a UITableViewController; the self.view is the
self.tableView. They point to the same thing so I guess one could also
use the self.tableView reference above.
Sometime later... In response to a UIControl event I call this method.
#IBAction func toggleRatingView (sender:AnyObject?){
//REQUIRED
self.ratingView.superview?.layoutIfNeeded()
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.37, initialSpringVelocity: 0.99, options: [.CurveEaseOut], animations: { () -> Void in
if CGRectContainsRect(self.view.frame, self.ratingView.frame) {
//in frame ~ animate away
//I play a sound to alert the user something is happening
self.centerYInflection.constant = self.aPointAboveScene
self.centerYInflection.priority = UILayoutPriority(950)
//I disable portions of the UI
self.disableUIElements(nil)
} else {
//out of frame ~ animate in
//I play a different sound here
self.centerYInflection.constant = 0
self.centerYInflection.priority = UILayoutPriority(950)
//I enable the UI fully
self.enableUIElements(nil)
}
//REQUIRED
self.ratingView.superview?.setNeedsLayout()
self.ratingView.superview?.layoutIfNeeded()
}) { (success) -> Void in
//do something else
}
}
These helper methods can be configured to control access to elements in your scene during the presentation of the view.
func disableUIElements(sender:AnyObject?) {
//UI
}
func enableUIElements(sender:AnyObject?) {
//UI
}
Caveats
My view is a custom view in the Storyboard (sitting outside of the
tableview but connected to the TableView Controller). The view has a
required user runtime attribute defined layer.zPosition with a Number value set to 2 (this ensures that it presents in front of the
UITableView).
One could also try playing around with bringSubviewToFront:
and sendSubviewToBack: methods if you don't want to set the zPosition
(I think zPosition is simpler to use)
Try this to hook a button at bottom of the UITableViewController
declare button as a variable:
var submitButton: UIButton!
and in viewDidLoad:
submitButton = UIButton(frame: CGRect(x: 5, y: UIScreen.main.bounds.size.height - 50, width: UIScreen.main.bounds.size.width - 10, height: 50))
submitButton.backgroundColor = UIColor.init(red: 180/255, green: 40/255, blue: 56/255, alpha: 1.0)
submitButton.setTitle("Submit", for: .normal)
submitButton.titleLabel?.font = UIFont(name: "Arial", size: 15)
submitButton.titleLabel?.textColor = .white
submitButton.addTarget(self, action: #selector(submit), for: .touchUpInside)
submitButton.layer.cornerRadius = 5
self.view.addSubview(submitButton)
and implement this method:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
submitButton.frame = CGRect.init(x: submitButton.frame.origin.x, y: UIScreen.main.bounds.size.height + scrollView.contentOffset.y - 50, width: submitButton.frame.width, height: submitButton.frame.height)
}
This works for me:
if let myTopView = Bundle.main.loadNibNamed("MyTopView", owner: self, options: nil)?.first as? MyTopView {
if let view = UIApplication.shared.keyWindow{
view.addSubview(myView);
myTopView.translatesAutoresizingMaskIntoConstraints = false
myTopView.topAnchor.constraint(equalTo: view.topAnchor ).isActive = true
myTopView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
myTopView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myTopView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}

iOS - UI Automation get textfield by accessibility label?

So in my nib file i enabled accessibility and set the accessibility label of a textfield to "txt"
I am trying to find this textfiled based on accessibility name and change its text.
var target = UIATarget.localTarget();
var application = target.frontMostApp();
var mainWindow = application.mainWindow();
mainWindow.logElementTree();
// This works
var textField = mainWindow.textFields()[0];
//this doesn't work
var textField = mainWindow.textFields()["txt"];
textField.setValue("Hello");
Am I doing anything wrong? How can I find a label based on accessibility label?
EDIT:
I am open to other solutions as well, I am trying to avoid getting the textfield based on the index
I have a similar problem and setting the accessibility label value in the code instead in the nib file solve my problem. For example, I have a UITextField that I need to access in my UIAutomation script, I would have to set the accessibility label value in the viewDidLoad method as shown below.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
testTxtField.accessibilityLabel = #"myTxtBox";
}
and below is my UIAutomation Script
var target = UIATarget.localTarget();
var application = target.frontMostApp();
var mainWindow = application.mainWindow();
mainWindow.logElementTree();
// This works
//var textField = mainWindow.textFields()[0];
// Now, this work too.
var textField = mainWindow.textFields()["myTxtBox"];
textField.setValue("Hello");
UIALogger.logMessage("Text field:" + textField.label());