How to hide a UIView and remove the "empty" space? -iOS/Monotouch - ios

I need to be able to hide controls on a page that uses constraints and remove the empty space that Hidden=true leaves. It needs to be similar to how the web handles visibility. If it's invisible, it doesn't take up space.
Does anyone know of a clean way to accomplish this?
Please let me know if you need more details.
Thx
Example:
UIButton | UIButton | UIButton
"empty space for hidden UIButton"
UIButton
That should really be rendered like this:
UIButton | UIButton | UIButton
UIButton
Edit: I'm using Xamarin Studio and VS2012 for development.

Since original question is related to Xamarin, I provide complete C# solution.
First, create height constraint for your view and give it an identifier in Xcode Interface Builder:
Then in controller override ViewDidAppear() method and wrap view with HidingViewHolder:
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
applePaymentViewHolder = new HidingViewHolder(ApplePaymentFormView, "ApplePaymentFormViewHeightConstraint");
}
It is important to create HidingViewHolder when view was laid out, so it has real height assigned.
To hide or show view you can use corresponding methods:
applePaymentViewHolder.HideView();
applePaymentViewHolder.ShowView();
HidingViewHolder source:
using System;
using System.Linq;
using UIKit;
/// <summary>
/// Helps to hide UIView and remove blank space occupied by invisible view
/// </summary>
public class HidingViewHolder
{
private readonly UIView view;
private readonly NSLayoutConstraint heightConstraint;
private nfloat viewHeight;
public HidingViewHolder(UIView view, string heightConstraintId)
{
this.view = view;
this.heightConstraint = view
.GetConstraintsAffectingLayout(UILayoutConstraintAxis.Vertical)
.SingleOrDefault(x => heightConstraintId == x.GetIdentifier());
this.viewHeight = heightConstraint != null ? heightConstraint.Constant : 0;
}
public void ShowView()
{
if (!view.Hidden)
{
return;
}
if (heightConstraint != null)
{
heightConstraint.Active = true;
heightConstraint.Constant = viewHeight;
}
view.Hidden = false;
}
public void HideView()
{
if (view.Hidden)
{
return;
}
if (heightConstraint != null)
{
viewHeight = heightConstraint.Constant;
heightConstraint.Active = true;
heightConstraint.Constant = 0;
}
view.Hidden = true;
}
}

In storyboard wire your constrains first. Then try this
self.viewToHideHeight.constant = 0;
self.lowerButtonHeightFromTop.constant = self.viewToHideHeightFromTop.constant + self.viewToHideHeight.constant;
[UIView animateWithDuration:0.5f animations:^{
self.viewToHide.alpha = 0.0f;
[self.view layoutIfNeeded];
}];

Related

Shadow in UIView cannot overlay Entry or Editor or Picker in Xamarin Forms

I added an RoutingEffect in Xamarin Form project and PlatformEffect in my Xamarin.iOS project. It will add effect to Stacklayout. The Stacklayout in this demo is a custom navigation bar. The below of navigation bar is a scrollview has many cells (label, entry, picker).
I implemented in Android is OK.
But in iOS has problem: Shadow effect cannot overlays some controls, such as: Entry, Editor, Picker. Could you share me how to fix it?
This is code in Xamarin.iOS project.
public class DropShadowEffect : PlatformEffect
{
protected override void OnAttached()
{
try
{
var effect = (myDemo.UIControls.DropShadowEffect)Element.Effects.FirstOrDefault(e => e is myDemo.UIControls.DropShadowEffect);
if (effect != null)
{
Container.Layer.CornerRadius = effect.Radius;
Container.Layer.ShadowColor = UIColor.Red.CGColor;// effect.Color.ToCGColor();
Container.Layer.ShadowOffset = new CGSize(effect.DistanceX, effect.DistanceY);
Container.Layer.ShadowOpacity = 0.8f;
Container.Layer.ShadowRadius = 2f;
Container.Layer.ShouldRasterize = true;
Container.Layer.MasksToBounds = false;
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: {0}", ex.Message);
}
}
*Shadow effect overly Label is OK
*Shadow effect cannot overlay either Picker or Entry
Cause:
Actually, such as Label will still overlay the shadow.But it doesn't seem obvious.If you set the background of label (such as red ),you will see the overlay.
Solution:
You can set the BackgroundColor of the Picker and Entry in the custom renderer to let the alpha as 0.
For example in EntryRenderer
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BackgroundColor = new UIColor(1,1,1,0);//The last parameter sets the alpha of backgound as transparent
Control.Layer.MasksToBounds = true;
Control.Layer.CornerRadius = xxx; //set the rounded corner
Control.Layer.BorderColor = UIColor.xxx.CGColor;
Control.Layer.BorderWidth = xxx;
}
}

Self Size Custom View from xib in Stack view in Xamarin iOS

I'm new at Xamarin development and I'm trying to display the list of comments for a specific ticket. The comments' body have different text size. I have a custom view TicketCommentRowUIView.xib:
using Foundation;
using System;
using UIKit;
using ObjCRuntime;
namespace Project.iOS
{
public partial class TicketCommentRowUIView : UIView
{
public TicketCommentRowUIView(IntPtr handle) : base(handle)
{
}
public static TicketCommentRowUIView CreateView(TicketCommentModel model)
{
TicketCommentRowUIView v = CreateView();
v.LoadModelInfo(model);
return v;
}
private static TicketCommentRowUIView CreateView()
{
var arr = NSBundle.MainBundle.LoadNib("TicketCommentRowUIView", null, null);
var v = Runtime.GetNSObject<TicketCommentRowUIView>(arr.ValueAt(0));
return v;
}
private void LoadModelInfo(TicketCommentModel model)
{
DateReply.Text = model.CreatedAt.ToShortDateString();
MessageReply.Text = model.Content;
ReplierImage.Image = UIImage.FromBundle("default_image_user");
}
}
}
And I'm adding that custom view to an stack view in TicketDetailViewController dinamically, but I don't know how to resize the custom view according its self size:
using Foundation;
using System;
using UIKit;
using System.Collections.Generic;
using System.Linq;
using CoreGraphics;
namespace Project.iOS
{
public partial class TicketDetailViewController : UIViewController
{
private nfloat TicketItemHeight = 200;
TicketModel _ticketItem;
List<TicketCommentModel> _ticketCommentsList;
TicketDetailPresenter _presenter;
public TicketDetailViewController (IntPtr handle) : base (handle)
{
_presenter = new TicketDetailPresenter(this);
}
public async override void ViewDidLoad()
{
base.ViewDidLoad();
ScrollView.LoadingStart();
await _presenter.LoadTicketComments(_ticketItem.Id);
FillComentsStackView();
ScrollView.LoadingComplete();
}
public void LoadMenuItem(TicketModel ticketItem)
{
_ticketItem = ticketItem;
}
public void FillComentsStackView()
{
foreach (TicketCommentModel item in _ticketCommentsList)
{
TicketCommentRowUIView itemView = TicketCommentRowUIView.CreateView(item);
itemView.HeightAnchor.ConstraintEqualTo(TicketItemHeight).Active = true;
CommentStackView.AddArrangedSubview(itemView);
}
}
}
}
My issue is that I don't know how to add an self size for each custom view, because the height of MessageReply UILabel it is variable... I put a constraint equals to 200 but when the text is too big it looks overlapped. And when it is a short text there is to much of blank space. Is there any way to do this?? In my android project I just set WrapContent layout params and it worked. But I can't find the way to apply that to the iOS project.
It seems you have set the Label's Line 0 to wrap its content.
Then you can set each item's constraint in your xib to make your custom view self size fit. From your screenshot you can set the left, top constraint of your image and top, left constraint of your DateReply, at last the left, top, right , bottom constraint of the MessageReply like:
Adjust the constant to feed your request. Moreover when you add the custom view in the TicketDetailViewController, there's no need to set the HeightAnchor.

Xamarin iOS subview created inside custom UIView class not showing up

I have a custom UIView class, where I would like to add a bunch of subviews. This should be a simple task but I can't seem to figure out why my views are not showing up on the UI.
The examples and tutorials out there are pretty straight forward and I believe I have implemented my code correctly. I have added a breakpoint at AddSubview and the container has a size.
The UIViewController class already has a background image added it via the storyboard.
Can someone help me see what I'm probably missing? I know it's something small.
Below is my code:
The custom view class
public class RotaryWheel : UIView
{
int numberOfSections;
public RotaryWheel(CGRect frame,int sections): base(frame)
{
numberOfSections = sections;
DrawWheel();
}
public void DrawWheel()
{
// derive the center x and y
float centerX = (float)(Frame.Width / 2);
float centerY = (float)(Frame.Height / 2);
container = new UIView();
container.Frame = new RectangleF(centerX, centerY, 100, 100);
container.BackgroundColor = UIColor.White;
AddSubview(container);
}
}
The UIViewController class where I initialize the view
public partial class HomePageController : UIViewController
{
public override void LoadView()
{
base.LoadView();
rotaryWheel = new RotaryWheel(new CGRect(20f, (float)(View.Frame.Height / 2), (float)View.Frame.Size.Width, (float)View.Frame.Height / 2f), 7);
View.AddSubview(rotaryWheel);
}
}
First at all, refer to loadView(). As the documentation mentioned
If you want to perform any additional initialization of your views, do so in the viewDidLoad() method.
We should never initialize subviews in LoadView, so I try to move that code to viewDidLoad
public override void ViewDidLoad()
{
base.ViewDidLoad();
RotaryWheel rotaryWheel = new RotaryWheel(new CGRect(20f, (float)(View.Frame.Height / 2), (float)View.Frame.Size.Width, (float)View.Frame.Height / 2f), 7);
View.AddSubview(rotaryWheel);
}
But it looks like this, the subviews doesn't locate as expected.
After moving that code to ViewDidAppear, the problem disappeared.
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
if(rotaryWheel == null){
rotaryWheel = new RotaryWheel(new CGRect(20f, (float)(View.Frame.Height / 2), (float)View.Frame.Size.Width, (float)View.Frame.Height / 2f), 7);
View.AddSubview(rotaryWheel);
}
}
Summary:
The real size of View comes out in method ViewDidAppear. If you don't use autoLayout , you should manage the View Frame carefully.
And about why you didn't see the subview, I guess you selected the iphone4 ,iPhone 5 or iPhone5S simulator for test , the screen width is 320, you create RotaryWheel with X= 20 , centerX is 300 in method LoadView, so it is displayed out of screen.

Xamarin iOS - Fit scrollview's contents within the scrollview

This may be a fairly simple solution but none of the suggestions here work for me. I have a UIScrollView inside a UITableViewCell. I'm adding dynamic images from a list into the scrollview like this.
public void InitViews()
{
scrollViewThumbNails.ContentSize =
new SizeF((float)scrollViewThumbNails.Frame.Size.Width/listCount * listCount,
(float)scrollViewThumbNails.Frame.Size.Height);
for (int i = 0; i < listCount; i++)
{
var imageView = new UIImageView
{
Frame = new RectangleF((float)i * (float)scrollViewThumbNails.Frame.Size.Width / listCount, 0,
(float)scrollViewThumbNails.Frame.Size.Width / listCount, (float)scrollViewThumbNails.Frame.Size.Height),
UserInteractionEnabled = true,
ContentMode = UIViewContentMode.ScaleAspectFit
};
//call method to load the images
var index = i;
imageView.SetImage(
url: new NSUrl(allItems[i].AbsoluteUri),
placeholder: UIImage.FromFile("placeholder.png"),
completedBlock: (image, error, type, url) =>
{
//when download completes add it to the list
if (image != null)
{
allImages.Add(image);
scrollViewThumbNails.AddSubview(imageView);
}
});
}
I found a suggestion here which advices to set the content size of the scrollview, based on the total number of subviews in ViewDidAppear like this:
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
CGRect contentRect = CGRect.Empty;
foreach(UIImageView view in scrollViewThumbNails.Subviews)
{
contentRect = CGRect.Union(contentRect,view.Frame);
}
scrollViewThumbNails.ContentSize = contentRect.Size;
}
The imageViews are still spaced out and do not start from the edge of the screen/scrollview as shown in my screen shot below. I would like for the first image to always be positioned at the origin of the scrollview and wouldn't want the spacing between each image. How can I adjust the scrollview based on the size of its contents?
Can someone show me what I'm missing? Thanks.
I found a solution here where I calculate the total height and width of all the views and assign that as the content size of the scroll view in ViewDidLayoutSubviews:
Solution
public override void ViewDidLayoutSubviews ()
{
base.ViewDidLayoutSubviews ();
try{
float scrollViewHeight = 0.0f;
float scrollViewWidth = 0.0f;
foreach(UIImageView view in scrollViewThumbnails.Subviews)
{
scrollViewWidth += (float)view.Frame.Size.Width;
scrollViewHeight += (float)view.Frame.Size.Height;
}
scrollViewThumbnails.ContentSize = new CGSize(scrollViewWidth, scrollViewHeight);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message+ex.StackTrace);
}
}

Custom UINavigationBar in MonoTouch

I'm currently trying to create a custom navigation-bar for my navigation-controller in iOS using MonoTouch. The application I'm developing has the need to have a single string available on-screen no matter where you are on the application, and I wan't to achieve something like this (I know this is a little out off proportion, but you should be able to get the point):
+-----------------------------------------+
| /---------| +---+ |
| / Back | Controller Name |btn| |
| \---------| +---+ |
+-----------------------------------------+
| Current building <- global string |
+-----------------------------------------+
The bottom bar should be as small as possible (while not being hard to read), and the original navigation-bar might need to be a tiny bit smaller than normal.
Also, I've tried not to use the designer, but to write all the UI-code myself, but if that is impossible to achieve this, then I'll off cause have to use the designer. Currently I've found a project for creating a custom UINavigationBar in MonoTouch, but I have no idea of how to apply that to my NavigationController (see as the NavigationBar-property is read-only). The project I was talking about can be found here: https://github.com/mafis/Monotouch-Custom-Control/tree/master/CustomControls. Also, I would like the design of the actual navigationbar (the top part) to be standard iOS design, and work as a navigationbar normally would.
Any hints, or pointers at how to do this would be appreciated.
This is how I ended up solving this problem. I subclassed UIViewController like this:
using System;
using MonoTouch.UIKit;
using MonoTouch.CoreGraphics;
using System.Drawing;
using System.Collections.Generic;
using FdvWeb.Core;
namespace FdvWeb
{
[MonoTouch.Foundation.Preserve(AllMembers=true)]
public class MainNavigationController : UINavigationController
{
private const float NAV_BAR_HEIGHT = 44;
private readonly float buildingBarTop = 44;
private readonly float buildingBarHeight = 17;
private readonly float viewOffset;
private readonly HashSet<UIViewController> modifiedViewControllers = new HashSet<UIViewController>();
UITextView buildingTextView;
[MonoTouch.Foundation.Preserve]
public MainNavigationController ()
{
viewOffset = buildingBarTop + buildingBarHeight - NAV_BAR_HEIGHT;
var tt = new UITextView (new RectangleF (0, buildingBarTop, 320, buildingBarHeight));
NavigationBarHidden = false;
NavigationBar.AddSubview (tt);
tt.Font = UIFont.BoldSystemFontOfSize (12);
tt.TextAlignment = UITextAlignment.Center;
tt.TextColor = UIColor.LightTextColor;
tt.BackgroundColor = UIColor.ViewFlipsideBackgroundColor;
tt.Editable = false;
tt.ContentInset = new UIEdgeInsets (-9, 0, 0, 0);
buildingTextView = tt;
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
}
public string BuildingText
{
get {
return buildingTextView.Text;
}
set {
container.Resolve<IUIThreadDispatcher> ().DispatchOnUIThread (delegate { // Run on UI thread
buildingTextView.Text = value;
});
}
}
public override void PushViewController (UIViewController viewController, bool animated)
{
if (!modifiedViewControllers.Contains (viewController))
{
viewController.View = new PaddedView (viewController.View, new InnsetF (0, viewOffset, 0, 0));
modifiedViewControllers.Add (viewController);
}
viewController.NavigationItem.RightBarButtonItem = pickBuildingItem;
base.PushViewController (viewController, animated);
}
private class PaddedView : UIView
{
private UIView view;
private InnsetF innsets;
public PaddedView (UIView view, InnsetF innsets)
: base(view.Frame)
{
this.view = view;
this.innsets = innsets;
this.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
this.AddSubview (view);
}
public override void LayoutSubviews ()
{
//apply the insets to the subview
view.Frame = new RectangleF (innsets.Left, innsets.Top,
Frame.Size.Width - innsets.Left - innsets.Right,
Frame.Size.Height - innsets.Top - innsets.Bottom);
}
}
private class InnsetF
{
private float top, bottom, left, right;
public InnsetF (float left, float top, float right, float bottom)
{
this.top = top;
this.left = left;
this.bottom = bottom;
this.right = right;
}
public float Top
{
get { return top; }
set { top = value; }
}
public float Bottom
{
get { return bottom; }
set { bottom = value; }
}
public float Right
{
get { return right; }
set { right = value; }
}
public float Left
{
get { return left; }
set { left = value; }
}
}
}
}
So you want the reverse of what Spotify does when it is offline (the string message you're referring to appears above the UINavigationController).
I don't think the approach you're taking is going to work - as far as I know, the NavigationBar can only be 44 pixels high. Could you take a similar approach to Spotify and move it above the NavigationBar? This way you could leave the NavigationController etc as is and simply make it's view smaller, then add the string to the window object - that way it will persist as you navigate through your application.

Resources