I'm new to ios development. I am trying to create a vertical ScrollView in a Xamarin iOS application.
Below is my code for a horizontal ScrollView
using System;
using UIKit;
using Foundation;
using CoreGraphics;
using System.Collections.Generic;
namespace TestApp
{
public partial class ViewController : UIViewController
{
public ViewController (IntPtr handle) : base (handle)
{
ScrollingButtonsController ();
}
UIScrollView scrollView;
List<UIButton> buttons;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
//MyScrollView.scrollEnabled=YES;
//MyScrollView.contentSize= CGSizeMake(CGFloat.width, CGFloat.height);
//end
nfloat h = 50.0f;
nfloat w = 50.0f;
nfloat padding = 10.0f;
nint n = 100;
scrollView = new UIScrollView {
Frame = new CGRect (0, 100, View.Frame.Height, h + 2 * padding),
ContentSize = new CGSize ((w + padding) * n, h),
BackgroundColor = UIColor.Red,
AutoresizingMask = UIViewAutoresizing.FlexibleWidth
};
for (int i=0; i<n; i++) {
var button = UIButton.FromType (UIButtonType.RoundedRect);
button.SetTitle (i.ToString (), UIControlState.Normal);
button.Frame = new CGRect (padding * (i + 1) + (i * w), padding, w, h);
scrollView.AddSubview (button);
buttons.Add (button);
}
View.AddSubview (scrollView);
//UIScrollView scrollView;
//scrollView = new UIScrollView (
// new CGRect (0, 0, View.Frame.Width, View.Frame.Height));
// View.AddSubview (scrollView);
}
public void ScrollingButtonsController ()
{
buttons = new List<UIButton> ();
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
}
}
I want to create a vertical scrollview and add some scrollable Text Views. Any idea on how can to do this?
I would be grateful if you can specify in detail how can I get the elements in my Main.storyboard file with structure scheme as per below to scroll:
-Scroll View
--label
---Image View
---Text View
--label
---Image View
---Text View
--label
---Image View
---Text View
--label
---Image View
---Text View
--label
---Image View
---Text View
and more...
You need to set ScrollView Content Size Height, larger than it's frame height.So it will scroll up/down. Width of scroll view should be the same as its content width, so it won't scroll right/left.
I was searching for this. I used many tutorials both with and without code. The best way to do the scrolling is the following:
Add the scrollview (left, right, up, down constraints)
Add whatever you want
The tricky part is to set 2 constraints:
1) horizontal space to container, where container is the scroll view (do this with you wider element)
2) vertical space to container, where container is the scroll view (do this with your last element)
While your screen becomes taller, the vertical constraint will increase your scroll view. No code needed, just the proper constraints.
In your example you have to:
set up, left and right constraints to your first label
add vertical constraints amongst all your labels
add the final vertical constrain between your final textview and the scrollview (the textview is possible to need a height constraint)
If your elements don't fit the screen in Xcode, use freeform size to make your controller bigger (this applies only to xcode, not on your actual program)
Related
I'm trying to follow the UIScrollView And Autolayout Mixed approach.
I've seen other similar questions but with no accepted answer.
I'm not sure that this is what I need.
Please understand that I'm neither working with Storyboards nor XIBs.
All my views are created programmatically using c# in Xamarin.iOS. This is very similar to objective C code.
Like many others I can't find a way to have my scroll view to actually scroll.
So in my main View Controller I have the following in ViewDidLoad():
public override void ViewDidLoad()
{
base.ViewDidLoad();
_scrollView = new UIScrollView
{
ShowsHorizontalScrollIndicator = false,
TranslatesAutoresizingMaskIntoConstraints = false,
AlwaysBounceVertical = true,
Bounces = true
};
_refreshControl = new UIRefreshControl { TranslatesAutoresizingMaskIntoConstraints = false };
_refreshControl.Enabled = true;
_refreshControl.ValueChanged -= RefreshControl_ValueChanged;
_refreshControl.ValueChanged += RefreshControl_ValueChanged;
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
_scrollView.RefreshControl = _refreshControl;
}
else
{
_scrollView.AddSubview(_refreshControl);
}
this.View.AddSubview(_scrollView);
_scrollViewContainer = new UIView { TranslatesAutoresizingMaskIntoConstraints = true };
_scrollView.AddSubview(_scrollViewContainer);
_scrollView.Anchor(top: this.View.SaferAreaLayoutGuide().TopAnchor, leading: this.View.LeadingAnchor, bottom: this.View.SaferAreaLayoutGuide().BottomAnchor, trailing: this.View.TrailingAnchor);
}
Nothing really fancy, I create my UIScrollView add it as a sub view of the main View. Create a UIRefreshControl and add it to the scroll view. Create and add a plain UIView to the UIScrollView in order to put all my subviews.
Because I'm using AutoLayout the ScrollView is anchored within constraints of the main view.
Now later in ViewWillAppear(), I'm creating Card Views that are added to the plain UIView. These card views are using autoloayout to be stacked on top of each other, the first card is anchored to the container view's top, leading and trailing anchors.
Everything in these cards is using autolayout, to basically stack everything.
public UIView BuildQOLCard()
{
CardView qolCard = new CardView();
_scrollViewContainer.AddSubview(qolCard);
qolCard.TranslatesAutoresizingMaskIntoConstraints = false;
qolCard.CornerRadius = 5f;
qolCard.ShadowOffsetHeight = 0;
qolCard.ShadowOffsetWidth = 0;
qolCard.BackgroundColor = UIColor.White;
...
qolCard.Anchor(leading: _scrollViewContainer.LeadingAnchor, trailing: _scrollViewContainer.TrailingAnchor, top: _scrollViewContainer.TopAnchor, padding: new UIEdgeInsets(10f, 10f, 10f, 10f));
return qolCard;
}
Of course the container view won't magically know how to size itself. So I'll have to give it a size otherwise I won't see anything.
So I mage a helper like this:
public void SizeScrollViewContentSize()
{
nfloat scrollViewHeight = 0.0f;
foreach (UIView view in this._scrollViewContainer.Subviews)
{
scrollViewHeight += view.Frame.Size.Height;
}
this._scrollViewContainer.Frame = new CGRect(0, 0, this._scrollView.Frame.Width, scrollViewHeight);
}
But I can't seem to figure out when to call it because the subviews are here but their sizes are unknown. I tried calling it from ViewDidLayoutSubviews().
Any help appreciated.
EDIT : Applying te recommandations of the accepted answer, led to scrolling being enabled but the scroll view always bounces back to top.
Here is the code right now:
public override void ViewDidLoad()
{
base.ViewDidLoad();
_scrollView = new UIScrollView
{
ShowsHorizontalScrollIndicator = false,
TranslatesAutoresizingMaskIntoConstraints = false
};
if (!UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
_scrollView.AlwaysBounceVertical = true;
_scrollView.Bounces = true;
}
_refreshControl = new UIRefreshControl { TranslatesAutoresizingMaskIntoConstraints = false };
_refreshControl.Enabled = true;
_refreshControl.ValueChanged -= RefreshControl_ValueChanged;
_refreshControl.ValueChanged += RefreshControl_ValueChanged;
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
_scrollView.RefreshControl = _refreshControl;
}
else
{
_scrollView.AddSubview(_refreshControl);
}
this.View.AddSubview(_scrollView);
_scrollViewContainer = new UIView { TranslatesAutoresizingMaskIntoConstraints = false };
_scrollView.AddSubview(_scrollViewContainer);
_scrollView.Anchor(top: this.View.SaferAreaLayoutGuide().TopAnchor, leading: this.View.LeadingAnchor, bottom: this.View.SaferAreaLayoutGuide().BottomAnchor, trailing: this.View.TrailingAnchor);
// Only define the width of the container
_scrollViewContainer.TopAnchor.ConstraintEqualTo(_scrollView.TopAnchor).Active = true;
_scrollViewContainer.LeadingAnchor.ConstraintEqualTo(_scrollView.LeadingAnchor).Active = true;
_scrollViewContainer.WidthAnchor.ConstraintEqualTo(_scrollView.WidthAnchor).Active = true;
}
Card one:
CardView qolCard = new CardView();
_scrollViewContainer.AddSubview(qolCard);
qolCard.TranslatesAutoresizingMaskIntoConstraints = false;
qolCard.CornerRadius = 5f;
qolCard.ShadowOffsetHeight = 0;
qolCard.ShadowOffsetWidth = 0;
qolCard.BackgroundColor = UIColor.White;
...
qolCard.Anchor(leading: _scrollViewContainer.LeadingAnchor, trailing: _scrollViewContainer.TrailingAnchor, top: _scrollViewContainer.TopAnchor, padding: new UIEdgeInsets(10f, 10f, 10f, 10f));
card 2:
CardView goalsCard = new CardView();
_scrollViewContainer.AddSubview(goalsCard);
goalsCard.TranslatesAutoresizingMaskIntoConstraints = false;
goalsCard.CornerRadius = 5f;
goalsCard.ShadowOffsetHeight = 0;
goalsCard.ShadowOffsetWidth = 0;
goalsCard.BackgroundColor = UIColor.White;
...
// Top should be constrained to the previous CardView's Bottom
goalsCard.Anchor(leading: _scrollViewContainer.LeadingAnchor, trailing: _scrollViewContainer.TrailingAnchor, top: qolCard.BottomAnchor,padding: new UIEdgeInsets(10f, 10f, 10f, 10f));
Final anchor:
// constraint the last card bottom to the bottom of the scrollview container
goalsCard.Anchor(bottom: context._scrollViewContainer.BottomAnchor);
The Anchor Helper:
internal static void Anchor(this UIView uIView, NSLayoutYAxisAnchor top = null, NSLayoutXAxisAnchor leading = null, NSLayoutYAxisAnchor bottom = null, NSLayoutXAxisAnchor trailing = null, UIEdgeInsets padding = default, CGSize size = default)
{
uIView.TranslatesAutoresizingMaskIntoConstraints = false;
if (top != null)
{
uIView.TopAnchor.ConstraintEqualTo(top, padding.Top).Active = true;
}
if (leading != null)
{
uIView.LeadingAnchor.ConstraintEqualTo(leading, padding.Left).Active = true;
}
if (bottom != null)
{
uIView.BottomAnchor.ConstraintEqualTo(bottom, -padding.Bottom).Active = true;
}
if (trailing != null)
{
uIView.TrailingAnchor.ConstraintEqualTo(trailing, -padding.Right).Active = true;
}
if (size.Width != 0)
{
uIView.WidthAnchor.ConstraintEqualTo(size.Width).Active = true;
}
if (size.Height != 0)
{
uIView.HeightAnchor.ConstraintEqualTo(size.Height).Active = true;
}
}
A scroll view needs its Frame defined and it needs its Content defined. If the content is constrained properly, scrolling is automatic.
I'll go through this step-by-step - using Storyboard just so we can see what's happening. Absolutely no different when we do it through code.
Start with a View Controller, add a scroll view, and constrain it to all four sides (I gave it a green background so we can see it):
Now we add a "container" view (cyan background), and constrain all 4 sides to the scroll view:
If we run this, we see:
That's because the "container" has no width or height. Its Leading / Trailing / Top / Bottom constraints are defining the scroll view's content.
So, let's constrain it Equal Width and Equal Height to the scroll view:
run it, and we see this:
Which is fine, except... it will never scroll, because it is the same height as the scroll view.
So, I delete the Equal Height - because I want the container's content to determine its height.
I add a "CardView" and set its Top / Leading / Trailing constraints to Top / Leading / trailing of "container", with 10-pts "padding" and a Height of 100. I also give it a Bottom Constraint to the Bottom of "container" (+10) - which will "pull the bottom of the container" up to 10-pts below the bottom of the CardView:
resulting in:
Now I add a second CardView, constrain Leading / Trailing constraints to Leading / trailing of container, with 10-pts "padding" and a Height of 100. And I set its Top constraint to the Bottom of CardView-1 (+10), and I constrain its Bottom to the Bottom of "container" (+10):
Unfortunately, we now have a 10-pt constraint from Bottom of CardView-1 and a 10-pt constraint from Bottom of CardView-2 - which, of course, won't work.
So, let's delete the "Bottom to Superview" constraint from CardView-1
and we get this:
Let's repeat those steps for a few more card views, each time constraining Leading / Trailing to "container", Height of 100, Top to Bottom of previous CardView... and then constrain only the Bottom of the last CardView to the Bottom of "container":
and Yay! We can scroll:
Doing this via code is just a matter of remembering that each CardView's Top should be constrained to the previous CardView's Bottom -- except for the first card, which you constrain to the Top of "container" and the last card, which you constrain to the Bottom of "container".
All done with auto-layout. No need to do any "calculate height and set frame" code.
As a side note, you can use a StackView and get rid of most of that:
forget the "container"
add a stack view to the scroll view
axis: vertical, spacing: 10
constrain all 4 sides of the stack view to the scroll view with 10-pts padding
give the stack view a Width constraint equal to scroll view width -20 (for the 10-pts padding on each side)
Then, just add your CardViews as arrangedSubviews of the stack view.
And.... Done!
For example I set the window size to 800x800 also the scroll view size is 800x800
But in this case when the content is not expanded so the content is on the top but the rest of the window is empty. I could resize the window now on my own but then when I expand the content I will have to resize the window again.
Visual it looks bad.
Then when expanding the content :
Now the content is over too much the window size so now the scroll view is show up :
But now I have another problem. The scroll view vertical is not working. When trying to scroll down it keep moving the scroller back up. It never move down.
And if I will collapse back up the content it will look like again like in the first screenshot.
In general my problem is how to use the window size with the scroll view and the content so it will not look like in the first screenshot and that the scroll view will work when it show up.
The editor window script :
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
public class ConversationsEditorWindow : EditorWindow
{
Vector2 scrollPos;
[MenuItem("Window/Editor Window Test")]
static void Init()
{
const int width = 800;
const int height = 800;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
GetWindow<ConversationsEditorWindow>().position = new Rect(x, y, width, height);
}
void OnGUI()
{
GameObject sel = Selection.activeGameObject;
ConversationTrigger targetComp = sel.GetComponent<ConversationTrigger>();
if (targetComp != null)
{
EditorGUILayout.BeginVertical();
EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(800), GUILayout.Height(800));
var editor = Editor.CreateEditor(targetComp);
var tar = editor.targets;
editor.OnInspectorGUI();
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
}
}
Your scroll view isn't working because need to save the current scroll position:
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(800), GUILayout.Height(800));
I have an application in Xamarin Forms, and I need that the user can choose one image from below and drag anywhere he wants to in the top view, the idea is: The below view with the images are the home rooms, and the top view is the Houseplant, the user can create his houseplant by dragging and rotating the images, and then finally save the top view as a jpg or png image.
I've searched here and 2 3 pages of google about drag and etc, but I haven't found anything that could help me with that, I tried pan gesture, tap gesture, but no success =[
Sorry if it is duplicated or something, this is my first post, and I really couldn't find anything.
How can I get this working in Xamarin.Forms or at least with custom renderers and etc?
Thank you guys.
Sample image of what I need
For your image in XAML:
<Image Source="plant.png" x:Name="image"/>
You can actually use pan gesture recognizers to drag and drop images in C#:
Define variables:
double x; // totalX for the pan gesture
double y; // totalY for the pan gesture
Initialize pan gesture and add it to the image:
PanGestureRecognizer panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanUpdated;
image.GestureRecognizers.Add(panGesture);
The event handler for the gesture:
void PanUpdated(object sender, PanUpdatedEventArgs args)
{
if (args.StatusType.Equals(GestureStatus.Running)) {
x = args.TotalX;
y = args.TotalY;
image.TranslateTo(x, y, 1);
}
else if (args.StatusType.Equals(GestureStatus.Completed)) {
// set the layout bounds of the image to the new position
// method varies depending on what type of layout you are using for the image
// eg. assume the image is in an absolute layout
// where the layout height is the screen height
// and the layout width is the screen width
Task.Run(() => {
Device.BeginInvokeOnMainThread(async () => // run UI task on main thread
{
await Task.Delay(50); // avoid flickering
var screenWidth = Application.Current.MainPage.Width;
var screenHeight = Application.Current.MainPage.Height;
var b = image.Bounds;
var newBounds = new Rectangle(b.X + x, b.Y + y, b.Width, b.Height);
var newAbsoluteBound =
new Rectangle(newBounds.X / (screenWidth - newBounds.Width),
newBounds.Y / (screenHeight - newBounds.Height),
newBounds.Width / screenWidth,
newBounds.Height / screenHeight);
// set new absolute bounds so a new TranslateTo can be applied
AbsoluteLayout.SetLayoutBounds(image, newAbsoluteBound);
await image.TranslateTo(0, 0, 0);
});
});
}
}
Make sure your page or Image is not in a scrollView.
If ScrollView is enabled for both orientations, Pan-Gesture wont work.
.
.
I have a subclassed parent UIView object which should add another subclassed UIView. This is the UIView I want to add and where the Draw method is not called:
public class Circle : UIView
{
private UIColor color;
public Circle ()
{
this.color = UIColor.Black;
this.BackgroundColor = UIColor.Clear;
}
public Circle (UIColor color)
{
this.color = color;
this.BackgroundColor = UIColor.Clear;
}
public override void Draw (CGRect rect)
{
base.Draw (rect);
// Get the context
CGContext context = UIGraphics.GetCurrentContext ();
context.AddEllipseInRect (rect);
context.SetFillColor (color.CGColor);
context.FillPath ();
}
}
This is how I'm adding the circle:
Circle circle = new Circle (UIColor.Red);
circle.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview (circle);
AddConstraint(NSLayoutConstraint.Create(circle, NSLayoutAttribute.Left, NSLayoutRelation.Equal, line, NSLayoutAttribute.Left, 1, 10));
AddConstraint(NSLayoutConstraint.Create(circle, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, line, NSLayoutAttribute.CenterY, 1, 0));
AddConstraint(NSLayoutConstraint.Create(circle, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1, 6));
AddConstraint(NSLayoutConstraint.Create(circle, NSLayoutAttribute.Width, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1, 6));
This code above is again in the Draw method of the parent. The objects in the parent are drawn fine except the circle and even if I use the below code as circle it is shown correctly. So the constraints are ok.
UIView circle = new UIView() { BackgroundColor = UIColor.Red };
What I'm doing wrong? Can't I override both Draw methods (in the subclassed parent and in the subclassed circle)?
PS: I have to mention that the circle should overlaps a line. But the Draw is never called so it seems that it doesn't get a frame.
Are you aware that you are instantiating an UIView and not a Circle in this code snipped ?
UIView circle = new UIView() { BackgroundColor = UIColor.Red };
Also you shouldn't add subview in the draw method, because it will be called multiple time, in fact you should only override the Draw method with you are doing a custom draw (it´s the case of the Circle view, but not the case of the parent view).
From apple documentations:
View drawing occurs on an as-needed basis. When a view is first shown,
or when all or part of it becomes visible due to layout changes, the
system asks the view to draw its contents. For views that contain
custom content using UIKit or Core Graphics, the system calls the
view’s drawRect: method
So can you post the code snipe where you actually add the parent view? And the code of the parent view, you might have override a method and are not calling the base class method (like setNeedsDisplay or something similar) OR you are not adding the view.
I created custom class LoginView.cs UIView For username and password added the loginButton.
now I added UIView Call into my LoginViewController.cs Class
When i build and run the application it shows the login box, two text fields and a button. The actions on the textfield are not working. When the textbox has focus the keyboard doesn't load
This is my custom View class for loginview.cs
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace DemoHouse
{
class LoginView : UIView
{
UIView view_Login;
UITextField txt_username;
public readonly UITextField txt_password;
public readonly UIButton btn_Login;
public LoginView (string loginView){
float frame = UIScreen.MainScreen.Bounds.Width;
Add (view_Login = new UIView (new RectangleF (20, 60,frame-40, 200)) {
BackgroundColor = UIColor.Red,
});
float subFrame = view_Login.Bounds.Width;
view_Login.AddSubview(txt_username = new UITextField (new RectangleF (20, 20, subFrame-40, 31)){
BackgroundColor = UIColor.White,
});
view_Login.AddSubview(txt_password = new UITextField (new RectangleF (20, 70, subFrame-40, 31)){
BackgroundColor = UIColor.White
});
view_Login.AddSubview (btn_Login = new UIButton (new RectangleF (20, 120, 60, 31)) {
BackgroundColor = UIColor.Blue
});
}
}
}
This is LoginViewController.cs
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace DemoHouse
{
public partial class LoginViewController : UIViewController
{
LoginView loginView;
UIScrollView scrollView;
public override void DidReceiveMemoryWarning (){
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
#region View lifecycle
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
loginView = new LoginView("LoginView");
View.AddSubview (loginView);
// Perform any additional setup after loading the view, typically from a nib.
}
Let me know if something is wrong.
Is this the correct approach?
I do not want to use storyboard and xib in my application.
#All
Thanks in advance.
Might be a silly solution, but add a tap gesture to the button.
login_btn.UserInteractionEnabled = true;
login_btn.AddGestureRecognizer (new UITapGestureRecognizer(()=>{
//Action In HERE
}));
The keyboard popping up is a native functionality, so I am in awe you managed to break it. Maybe you should try not to condensate your code so much. It makes for less lines of code, but also causes readability and complexity to increase. Also, why not merge both classes? You already have a ViewController class. Try something like this:
public partial class LoginViewController : UIViewController
{
private UIScrollView scrollView;
private UITextField txt_username, txt_password;
private UIButton btn_login;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
/*Depending on what else is on the screen, you can hard-code the
x, y, width and height, but in all other cases, use this:*/
scrollView = new UIScrollView (this.View.Bounds);
txt_username = new UITextField (new RectangleF (5, 5, 50, 25));
//Change other properties of the UITextfield to your liking here.
txt_password = new UITextField (new RectangleF (5, 35, 50, 25));
//Change other properties of the UITextfield to your liking here.
btn_login = new UIButton (new RectangleF (5, 65, 50, 25));
btn_login.TouchUpInside += Login;
//Change other properties of the UIButton to your liking here.
this.View.AddSubview (scrollView);
scrollView.AddSubviews (new UIView[]{txt_username, txt_password, btn_login});
}
void Login (object sender, eventargs e)
{
//Do something on button click
}
}
Keep in mind that with this solution, you won't actually be able to scroll down with the scrollView, because the scrollView.ContentSize is not set. You could implement a method, where the framesizes of all controls are set. You call this method when changing orientation (in the ViewDidRotate method). You can also add the scrollView.ContentSize in this method.
I hope this information helps. Good luck!