I'm having an issue asynchronously loading several UIWebViews into one Section. I'm using MonoTouch.Dialog to generate the UI. I am loading data from a blog and showing 10 items which consist of an image plus some HTML text. What's happening is that the posts are not all showing up and the ones that are showing up are out of order. Here's what I'm doing:
public partial class BlogViewController : DialogViewController
{
private Section mainSection;
public BlogViewController () : base (UITableViewStyle.Grouped, null)
{
Root = new RootElement ("");
mainSection = new Section ("");
mainSection.Add(new ActivityElement ());
Root.Add (mainSection);
}
public override void ViewDidLoad()
{
base.ViewDidLoad ();
new Thread (new ThreadStart(PopulateBlog)).Start ();
}
private void PopulateBlog ()
{
var posts = service.GetPosts (currentOffset, 10);
InvokeOnMainThread (delegate {
foreach (var post in posts) {
//grab an appropriate image size
var altSize = post.photos [0].alt_sizes.Where (x => x.width < 401).OrderByDescending(x => x.width).FirstOrDefault ();
if (altSize != null) {
var img = LoadImageFromUri(altSize.url);
//scale the image, not really important
var imageView = new UIImageView (new RectangleF (0, 0, screenWidth, height));
imageView.Image = img;
var content = new UIWebView ();
//When the HTML finishes rendering figure out the size and add it to the section. Apparently can't figure the size ahead of time?
content.LoadFinished += (sender, e) =>
{
var contentHeight = Int32.Parse (content.EvaluateJavascript ("document.getElementById('content').offsetHeight;"));
content.Frame = new RectangleF (0, height + 10, screenWidth, contentHeight + 10);
//dynamically size this view to fit the content
var view = new UIView
(new RectangleF (0, 0,
screenWidth,
height + contentHeight));
view.AddSubview (content);
view.AddSubview (imageView);
//add the view to the Section which is later added to the Root
mainSection.Add(view);
};
var htmlString = #"some HTML here";
content.LoadHtmlString(someHtml);
content.ScrollView.ScrollEnabled = false;
content.ScrollView.Bounces = false;
}
}
});
Root.Reload(mainSection, UITableViewRowAnimation.None);
}
}
I'm guessing that A) the LoadFinished events are not happening in the same order that they are queued up, and that B) The Root.Reload gets called before they all fire. I tried spinning with a Thread.Sleep prior to the Root.Reload but then the LoadFinished events never even get fired.
I also tried putting all of the UIView elements in a Dictionary to be added after they are all populated but it seems like as soon as InvokeOnMainThread ends the LoadFinished event handler never gets called again.
Do you have to use MT.Dialog? I would personal try to use UITableView instead. Load the web view content, determine its size (maybe use sizeThatFits?) and push it to the data source at correct location. Then return the size on the UITableViewDelegate's heightForRowAtIndexPath call. UITableView should take care of the rest.
Related
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);
}
}
Stuart's N-06 Books sample is good for getting basic understanding about using MvxSimpleTableViewSource.
[Register("FirstView")]
public class FirstView : MvxViewController
{
public override void ViewDidLoad()
{
View = new UIView(){ BackgroundColor = UIColor.White};
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
EdgesForExtendedLayout = UIRectEdge.None;
var textField = new UITextField(new RectangleF(10, 10, 300, 40));
Add(textField);
var tableView = new UITableView(new RectangleF(0, 50, 320, 500), UITableViewStyle.Plain);
Add(tableView);
tableView.RowHeight = 88;
var source = new MvxSimpleTableViewSource(tableView, BookCell.Key, BookCell.Key);
tableView.Source = source;
var set = this.CreateBindingSet<FirstView, Core.ViewModels.FirstViewModel>();
set.Bind(textField).To(vm => vm.SearchTerm);
set.Bind(source).To(vm => vm.Results);
set.Apply();
tableView.ReloadData();
}
}
But how can resize the tableview's height according it's content once it loads data?
Not entirely sure what you want to do... Normally in an iOS UI, the tableview size is fixed regardless of its content.
However, if you did want to resize the table then you could:
Inherit from MvxTableViewSource or UITableView and provide some logic there
Or add a binding in your class to some View property TableCount, bind that property and then implement the sizing logic there. Something like:
set.Bind(this).For(v => v.TableCount).To(vm => vm.Results.Count);
private int _tableCount
public int TableCount {
get { return _tableCount; }
set {
// implement your sizing animations here (maybe animate constraints?)
}
}
Just to add to Stuart's answer, here is an example for the frame of the table:
int _tableHeight;
public int TableHeight
{
get { return _tableHeight; }
set
{
_tableHeight = value;
_myPlayers.Frame = _tableHeight > 0 ? new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, Dimens.TableRowHeight * _tableHeight) : new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, Dimens.TableRowHeight * BusinessConstants.GetBiggestPositionCount());
_myPlayers.ReloadData();
}
}
I want to implement a flyout navigation in Xamarin.iOS. For this purpose I am using the FlyoutNavigationController, which works good so far.
But now I have to display some additional content underneath the navigation list inside of the flyout-menu (basically an image, some labels and buttons). For this purpose, I wanted to use the FooterView property of the "section" control that holds the menu items.
When I set
section.Footer = "Test";
it will work (I can see the text), but when I use the 'FooterView' property, nothing shows up:
public class ViewController : UIViewController
{
FlyoutNavigationController navigation;
private const string PageNamePage1 = "The first page";
private const string PageNamePage2 = "The second page";
readonly string[] pages =
{
PageNamePage1,
PageNamePage2
};
public override void ViewDidLoad()
{
base.ViewDidLoad();
navigation = new FlyoutNavigationController
{
View =
{
Frame = UIScreen.MainScreen.Bounds
}
};
View.AddSubview(navigation.View);
var elements = pages.Select(page => new StringElement(page) as Element);
navigation.NavigationRoot = new RootElement("Induserv App");
var section = new Section();
section.AddAll(elements);
var uiTextView = new UITextView {Text = "--> This won't show!"};
section.FooterView = new UIView
{
uiTextView
};
navigation.NavigationRoot.Add(section);
navigation.ViewControllers = new UIViewController[]
{
new UINavigationController(new Page1Controller(navigation, PageNamePage1)),
new UINavigationController(new Page2Controller(navigation, PageNamePage2)),
};
}
}
Does anyone have an idea what it needs to display this footer here? Is there another way to put some controls in this panel?
Set a frame for both the footer view and uiTextView, like so:
var uiTextView = new UITextView(new RectangleF(0, 0, 10, 10)) {Text = "--> This won't show!"};
section.FooterView = new UIView(new RectangleF(0, 0, 10, 10))
{
uiTextView
};
I've got a UITableView located in a View attached to a SlidingPanel.
I'm using SlidingPanels.Lib, and I've got a custom presenter
Here's a gist of the custom presenter
From there my MenuView is really straight forward.
public class MenuView : ViewControllerBase
{
private new MenuViewModel ViewModel { get { return (MenuViewModel)base.ViewModel; } }
public override void ViewDidLoad()
{
base.ViewDidLoad();
// todo: this should actually be...
// _currentBookId = currentUser.UserBooks[0].BookId;
// _homeViewModel.ChapterViewModel = Mvx.Resolve<IChapterService>().FindByBookId(_currentBookId);
ViewModel.Chapters = Mvx.Resolve<IChapterService>().Find();
// this ensures the sliding panel doesn't fill the entire view.
var frame = View.Frame;
frame.Width = 300;
View.Frame = frame;
// var currentUser = Mvx.Resolve<IUserService>().GetById(Mvx.Resolve<UserModel>().Id);
var label = new UILabel(new RectangleF(10, 10, 300, 40))
{
TextColor = UIColor.White,
};
Add(label);
//var listHeight = (chapters.Count*40);
var navigationList = new UITableView(new RectangleF(0, 50, 300, 300))
{
Source = new NavigationTableSource(ViewModel.Chapters)
};
Add(navigationList);
var set = this.CreateBindingSet<MenuView, MenuViewModel>();
set.Bind(label).To(vm => vm.DisplayName);
set.Apply();
}
}
Unfortunately I'm not sure where to look in order to allow the TableView to scroll. I can see it, but it goes beyond the bottom of the screen.
I think the problem is in:
var listHeight = (chapters.Count*40);
Instead of this, try setting the height to the height of the available screen (which depends on which iPhone/iPad you are on). After this, then the list will scroll within that available height.
Took a couple of hours of dicking around to discover that the original source code that I grabbed from #patbonecrusher's Github repo had some differences from the lib that you can find at #fcaico's Github repo
Long story short... all I had to do was compile #fcaico's version and deploy it into my app, and the scrolling came back. Now I realize that #fcaico's repo just contains a submodule to #patbonecrusher's, but for some reason... the recompile fixed the issue. Don't have a lot of time to dig into the reason why.
I am facing an interesting issue. I have HTML data that I obtain from a Web service that I display inside the UIWebView and I want to open up hyperlinks in Safari when a user taps on the link.
The code I have works great for any links that are visible immediately within the Web View. I have a scroll on the View and links that are below the visible area (i.e. links that I have to scroll to) do not work when I tap them at all.
Please note that I'm disabling all scrollbars in the WebView using logic like so:
DescriptionWebView = new UIWebView(new RectangleF(0, separator.Frame.Bottom + 1, 320, 140));
DescriptionWebView.DataDetectorTypes = UIDataDetectorType.All;
DescriptionWebView.UserInteractionEnabled = true;
DescriptionWebView.Delegate = new PlanDescriptionWebViewDelegate(this);
foreach(UIView subview in DescriptionWebView.Subviews)
{
if(subview is UIScrollView)
(subview as UIScrollView).ScrollEnabled = false;
}
And I'm resizing the WebView based on the content size using logic inside of a delegate like so:
public override bool ShouldStartLoad(UIWebView webView, MonoTouch.Foundation.NSUrlRequest request, UIWebViewNavigationType navigationType)
{
NSUrl url = request.Url;
if(navigationType == UIWebViewNavigationType.Other)
{
if(url.Scheme.ToString().Equals("ready"))
{
float contentHeight = Convert.ToInt32(url.Host);
if(contentHeight < _infoView.DescriptionWebView.Frame.Height)
contentHeight = _infoView.DescriptionWebView.Frame.Height;
RectangleF scrollViewFrame = _infoView.ScrollView.Frame;
RectangleF descriptionViewFrame = _infoView.DescriptionWebView.Frame;
BeginInvokeOnMainThread(() =>
{
_infoView.DescriptionWebView.Frame = new RectangleF(descriptionViewFrame.X, descriptionViewFrame.Y, descriptionViewFrame.Width, contentHeight + 10);
//_infoView.ScrollView.ContentSize = new SizeF(scrollViewFrame.Size.Width, contentHeight + scrollViewFrame.Y + scrollViewFrame.Height - descriptionViewFrame.Height);
_infoView.ScrollView.ContentSize = new SizeF(scrollViewFrame.Size.Width, contentHeight + scrollViewFrame.Height + 20 - descriptionViewFrame.Height);
});
return false;
}
} else if(navigationType == UIWebViewNavigationType.LinkClicked)
{
UIApplication.SharedApplication.OpenUrl(url);
return false;
}
return true;
}
I am completely at a loss. Why would links on the top within view work, and the ones at the bottom where I have to scroll won't?
Any help would be very greatly appreciated.