在ViewController Segue

时间:2015-08-28 02:21:38

标签: ios uiscrollview xamarin segue viewcontroller

经过长时间的潜伏,这是第一个问题。我一直在使用Visual Studio在Xamarin下使用C#自学iOS开发。

我的问题陈述是:为什么我的滚动视图只能工作,即注册事件,例如拖动,使用ViewController segue到另一个视图后回来?虽然同时使用滚动浏览PageControl可以随时正常工作吗?

我已经成功地使用了Xamarin网站和其他地方发现的各种示例代码(包括一些用Swift编写的样本,我将其转换为C# - Objective-C也可能是Minoan Linear B )。

在我开始将AutoLayout添加到等式中之前,这在所有变体中都没有问题。使用FluentLayout使这变得更容易,我现在总体上拥有一个表现良好的用户界面,就像我想要的所有iPhone型号一样。

基本的样板代码工作得很好,因为它们都被卡在了根视图控制器的ViewDidLoad中,但现在我已经把它全部打破了#34;清理"他们表现出这种奇怪的行为。

我必须解决的一个问题是,在布局约束完成之前,尺寸不可用时,在UIImageView框架内获取图像以正确调整大小的问题。正如我现在理解这个概念,这就是ViewDidLayoutSubViews()的用途。

因此我的根VS代码:

namespace ScrollTest.iOS
{
public partial class RootViewController : UIViewController
{
    ...

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        View = new MainView();
    }

    public override void ViewDidLayoutSubviews()
    {
        base.ViewDidLayoutSubviews();

        CGRect pageFrame = SharedStatic.ScrollView.Frame;

        SharedStatic.ScrollView.ContentSize = new CGSize(pageFrame.Width * 2, pageFrame.Height);

        SharedStatic.ImageView.Frame = pageFrame;
        SharedStatic.ImageView.Image = UIImage.FromFile("toothless.jpg").Scale(pageFrame.Size);
        SharedStatic.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;

        pageFrame.X += SharedStatic.ScrollView.Frame.Width;

        SharedStatic.TableView.Frame = pageFrame;
        SharedStatic.TableView.ContentMode = UIViewContentMode.ScaleAspectFit;
    }
...
}

从向下的MainView()类开始,我有一个Views层次结构,它们被实例化,然后设置布局约束,如下所示:

namespace ScrollTest.iOS
{
[Register("MainView")]
internal class MainView : UIView
{
    public MainView()
    {
        Initialise();
    }

    public MainView(CGRect frame) : base(frame)
    {
        Initialise();
    }

    private void Initialise()
    {
        SharedStatic.MainView = this;

        var navView = new NavView();
        Add(navView);

        var contentView = new ContentView();
        Add(contentView);

        var pagerView = new PagerView();
        Add(pagerView);

        var toolView = new ToolView();
        Add(toolView);

        var buttonView = new ButtonView();
        Add(buttonView);

        const int padding = 1;
        var navHeight = 32;
        var pageControlHeight = 25;
        var toolHeight = 25;
        var buttonHeight = 20;

        this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        this.AddConstraints
        (
            ... other view constraints...

            contentView.Below(navView, padding),
            contentView.AtLeftOf(this),
            contentView.WithSameWidth(this),
            contentView.Bottom().EqualTo().TopOf(pagerView),

            pagerView.Above(toolView, padding),
            pagerView.AtLeftOf(this),
            pagerView.WithSameWidth(this),
            pagerView.Height().EqualTo(pageControlHeight),

            ...
        );
    }
}
}

实际的ScrollView类看起来像这样,事件处理程序只是连接到此测试的调试输出。

namespace ScrollTest.iOS
{
[Register("ContentView")]
internal class ContentView : UIScrollView
{
    private ContainerView _containerView;

    public ContentView()
    {
        Initialise();
    }

    public ContentView(CGRect frame) : base(frame)
    {
        Initialise();
    }

    private void Initialise()
    {
        SharedStatic.ScrollView = this;

        _containerView = ContainerView.Instance;
        Add(_containerView);

        PagingEnabled = true;
        ScrollEnabled = true;
        Bounces = false;
        DirectionalLockEnabled = true;

        DecelerationEnded += scrollView_DecelerationEnded;

        Scrolled += delegate { Console.WriteLine("scrolled"); };
        DecelerationStarted += delegate { Console.WriteLine("deceleration started"); };
        DidZoom += delegate { Console.WriteLine("did zoon"); };
        DraggingEnded   += delegate { Console.WriteLine("dragging ended"); };
        DraggingStarted += delegate { Console.WriteLine("dragging started"); };
        ScrollAnimationEnded += delegate { Console.WriteLine("Scroll animation ended"); };
        ScrolledToTop   += delegate { Console.WriteLine("Scrolled to top"); };
        WillEndDragging += delegate { Console.WriteLine("will end dragging"); };
        ZoomingEnded    += delegate { Console.WriteLine("zooming ended"); };
        ZoomingStarted += delegate { Console.WriteLine("zooming started"); };

        this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        this.AddConstraints
        (
            _containerView.AtLeftOf(this),
            _containerView.AtRightOf(this),
            _containerView.AtTopOf(this),
            _containerView.AtBottomOf(this)
        );
    }

    private void scrollView_DecelerationEnded(object sender, EventArgs e)
    {
        Console.WriteLine("Done changing page");
        nfloat x1 = SharedStatic.ImageView.Frame.X;
        nfloat x2 = SharedStatic.TableView.Frame.X;
        nfloat x = this.ContentOffset.X;
        if (x == x1)
        {
            Console.WriteLine("flip");
            SharedStatic.PageControl.CurrentPage = 0;
        }
        else
        {
            Console.WriteLine("flop");
            SharedStatic.PageControl.CurrentPage = 1;
        }
    }
}
}

"以下"它是一个容纳我的图像和表的容器视图,根据我对iOS编码实践的理解是这样做的方法(除了单例类,这是测试其他东西的结果)。

namespace ScrollTest.iOS
{
[Register("ContainerView")]
internal sealed class ContainerView : UIView
{
    private UIImageView _imageView;
    private UITableView _tableView;

    private static readonly Lazy<ContainerView> lazy = new Lazy<ContainerView>(() => new ContainerView());

    public static ContainerView Instance { get { return lazy.Value; } }

    private ContainerView()
    {
        Initialise();
    }

    private ContainerView(CGRect frame) : base(frame)
    {
        Initialise();
    }

    private void Initialise()
    {
        SharedStatic.ContainerView = this;

        Console.WriteLine("setting up scrolling stuff");

        _imageView = new UIImageView();
        Add(_imageView);
        SharedStatic.ImageView = _imageView;

        _tableView = new UITableView();
        Add(_tableView);
        SharedStatic.TableView = _tableView;

        this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        this.AddConstraints
        (
            _imageView.AtLeftOf(this),
            _imageView.AtTopOf(this),
            _imageView.AtBottomOf(this),
            _imageView.WithSameWidth(this),
            _tableView.Left().EqualTo().RightOf(_imageView),
            _tableView.WithSameTop(_imageView),
            _tableView.WithSameBottom(_imageView),
            _tableView.WithSameWidth(_imageView)
        );
    }
}
}

在我的屏幕顶部是一个带有汉堡按钮的导航栏,它将触发到另一个视图控制器的segue,在路上负责弹出一个选项屏幕。它还有另一个汉堡包按钮,可以自动关闭并返回主屏幕。这没有问题:

_navBar.SetItems(new UINavigationItem[] { new UINavigationItem { LeftBarButtonItem = new UIBarButtonItem(UIImage.FromFile("hamburger32x32.png"),     UIBarButtonItemStyle.Plain, BringUpOptions) } }, false);

事件处理程序:

internal async void BringUpOptions(object s, EventArgs e)
    {
        Console.WriteLine("Options clicked");
        var board = UIStoryboard.FromName("MainStoryboard", null);
        var optionController = board.InstantiateViewController("OptionsViewController");
        optionController.ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal;
        await this.Window.RootViewController.PresentViewControllerAsync(optionController, true);
    }

我只将故事板编辑器用于我的两个视图控制器和segue。所有其他控件/视图都以编程方式生成,布局也是如此。

将所有这些放在一起,会产生正确布局的用户界面,正确工作到选项屏幕,正确解雇/返回。

同时正在点击页面控件:在图像和表格视图之间来回翻转。

但是,尝试将图像视图拖动到表视图时,此分页不起作用!不是在app启动时!调试显示没有捕获任何事件。

直到我进入选项屏幕至少一次然后回来!然后一切都按预期工作:页面控制翻转/翻转和滚动视图来回滚动。

我完全失去了为什么会这样!初始segue初始化对于scrollview工作显然是必要的是什么?我用调试输出贴了我的代码,然后单步分散注意力。我无法找到它。它可能是Xamarin中的一个错误吗?

我是否需要在视图类中进行一些设置或调用,而这些设置或调用是我根本不知道的,因为它在使用故事板编辑器时通常是隐藏的?我已经超过了Xamarin的UIScrollView课程文档,但没有任何事情发生在我身上。

我认为我对这个共享静态类的使用有点尴尬可能是一个问题,但我不知道如何在各个类中获取布局信息(因此图像的大小调整失败)。我的目的是在我解决了这个问题之后,深入研究并删除了这个问题:

namespace ScrollTest.iOS
{
internal static class SharedStatic
{
    internal static MainView MainView { get; set; }
    internal static UIPageControl PageControl { get; set; }
    internal static ContentView ScrollView { get; set; }
    internal static ContentView ContentView => ScrollView;
    internal static ContainerView ContainerView { get; set; }

    internal static UIImageView ImageView { get; set; }
    internal static UITableView TableView { get; set; }
}
}

由于代码非常接近完美,我真的在想我错过了一些相当直接的东西。使用故事板时遇到的一些新手错误,但是却以某种方式咬我。

谢谢!

编辑20150828:经过一些进一步的研究后,我为UIApplication添加了SendEvent的覆盖:

[Register("TestApp")]
class TestApp : UIApplication
{
    public override void SendEvent(UIEvent uievent)
    {
        base.SendEvent(uievent);
        Console.WriteLine("event hit");
    }
}

然后将Main.cs更改为:

public class Application
{
    // This is the main entry point of the application.
    static void Main(string[] args)
    {
        UIApplication.Main(args, "TestApp", "AppDelegate");
    }
}

因此,我确实可以看到触摸/拖动/滑动导致事件命中,但是它们没有被正确识别/分类。钻进调试器后,我能够看到UIScrollView正在接收&#34; hits&#34;但他们什么都不做!基本上它完全忽略了它们 - 直到我完成第一个视图控制器segue,然后所有事件都正常启动。

这开始出现一个错误,但我根本不知道它确定它不在我的代码中或实际上在Xamarin中。

1 个答案:

答案 0 :(得分:0)

这让我受到了很多诅咒和尖叫,但我已经能够解决这个问题,修复了新出现的后续问题并使整个目标UI设计正常工作。我希望我的解决方案对其他人有所帮助。

首先,最后我绝对强制要求了解UIScrollView和Autolayout上经常提到的Apple技术说明的每一个细节。

Apple Technical Note TN2154

这很难,因为我不了解Objective-C,但通过使用我的Xamarin C#代码和Apple示例代码来回减少我的无知,我能够深入学习并真正理解滚动视图与自动布局约束之间的交互。我不愿意深入研究这个必要条件可能有助于我解决这个问题所需的时间。

虽然有大量的链接显示样本,但许多人使用故事板,这实际上让我的生活更加艰难。因人而异。和以前一样,我决定采用程序化路线并使用非常有用的

Cirrious FluentLayout on Github

我的解决方案的核心,最后是使用覆盖我的scrollview的LayoutSubviews()和子视图容器类来包含约束的设置(除了一些其他重构以将所有内容都集成到一个类中文件,使用“部分”来光学分解代码。

对于UIScrollView,我使用一个嵌入式UIView作为容器。 ctor只声明该类,但该容器的布局在LayoutSubviews()方法中处理!

[Register("ScrollView")]
    internal sealed class ScrollView : UIScrollView
    {
        internal ContainerView Container { get; }

        internal ScrollView()
        {
            Container = new ContainerView();
            Add(Container);
        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();

            this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
            this.AddConstraints
            (
                Container.AtTopOf(this),
                Container.AtLeftOf(this),
                Container.WithSameWidth(this),
                Container.WithSameHeight(this)
            );
        }
    }

我为我的2个滚动页面中包含的视图重复了这个:

[Register("ContainerView")]
    internal sealed class ContainerView : UIView
    {
        private UIImageView _arenaView;
        internal UIImageView Arena => _arenaView;

        private UIView _gridView;
        internal UIView Grid => _gridView;

        internal ContainerView()
        {
            _arenaView = new UIImageView();
            Add(_arenaView);

            _gridView = new UIView()
            Add(_gridView);
        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();

            // determine the size of the basic frame in the scrolling area
            CGRect pageFrame = SharedStatic.ScrollView.Frame;
            // set it's overall size to n times the width, where is number pages, in this case 2
            SharedStatic.ScrollView.ContentSize = new CGSize(pageFrame.Width * 2, pageFrame.Height);

            _arenaView.Frame = pageFrame;
            _arenaView.Image = UIImage.FromFile("someimage.jpg");

            // offset by the width to get to second page
            pageFrame.X += SharedStatic.ScrollView.Frame.Width;

            _gridView.Frame = pageFrame;

            this.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
            this.AddConstraints
            (
                _arenaView.WithSameTop(this),
                _arenaView.WithSameBottom(this),
                _arenaView.WithSameHeight(this),
                _arenaView.WithSameWidth(this),
                _gridView.Left().EqualTo().RightOf(_arenaView),
                _gridView.WithSameTop(_arenaView),
                _gridView.WithSameBottom(_arenaView),
                _gridView.WithSameWidth(_arenaView)
            );
        }

我理解这种方法的原因是因为直到各种子视图的布局发生,才确定了各种帧和大小。这意味着我必须在超级视图的布局中为我的图像和数据网格本身布局“动态”子视图,以便正确应用约束,同时仍然接收事件。

我把它全部放入根视图控制器的ViewDidLayoutSubViews()的原始方法导致错误的视图接收所有触摸输入,或者更糟糕的是没有正确地注册事件代理。在某种程度上,这仍然是“不知道为什么现在有效,但我该抱怨谁?”的案例。

剩下的问题是对LayoutSubviews()的不断重复调用。每次(甚至部分)滚动,触摸动作,segue都会反复调用此方法,无论是否需要更改布局。我发现了一个有用的教程,我很快就找不到了,这解释了保持状态信息并以智能方式最小化呼叫次数,我将在下一步进行实验。

可以在此处找到调用LayoutSubviews的信息:

When is the layoutSubviews method called?

和该帖子中的链接。

通过kludgy SharedStatic类的紧密耦合的可怕位也将被替换,但您可能会发现在测试和调试阶段使用这样的构造很有用。

希望这有助于那里的人!