mirror of
https://github.com/LukeHagar/jims-blog.git
synced 2025-12-10 04:20:17 +00:00
126 lines
26 KiB
HTML
126 lines
26 KiB
HTML
<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta http-equiv=x-ua-compatible content="ie=edge"><link rel=icon href=/fav.png type=image/png><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link rel=preload as=style href="https://fonts.googleapis.com/css2?family=Alata&family=Lora&family=Muli:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Roboto&family=Muli:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"><link rel=stylesheet href="https://fonts.googleapis.com/css2?family=Alata&family=Lora&family=Muli:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Roboto&family=Muli:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" media=print onload='this.media="all"'><noscript><link href="https://fonts.googleapis.com/css2?family=Alata&family=Lora&family=Muli:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Roboto&family=Muli:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel=stylesheet></noscript><link rel=stylesheet href=/css/font.css media=all><meta property="og:title" content="iOS pull to refresh without using a UITableView"><meta property="og:description" content="I’ve been working on the UI for my current app, trying to make some usability improvements. The main screen is a list of data containing an image and some text. Although this is a standard UI pattern, the problem I’m having with it is one of size. List rows are usually short and full width. For text this is fine, but not so good for images, then end up being small and not very easy to see."><meta property="og:type" content="article"><meta property="og:url" content="https://jimbobbennett.dev/blogs/ios-pull-to-refresh-without-using-a-uitableview/"><meta property="og:image" content="https://jimbobbennett.dev/blogs/ios-pull-to-refresh-without-using-a-uitableview/banner.png"><meta property="article:section" content="blogs"><meta property="article:published_time" content="2014-09-28T08:38:16+00:00"><meta property="article:modified_time" content="2014-09-28T08:38:16+00:00"><meta property="og:site_name" content="JimBobBennett"><meta name=twitter:card content="summary_large_image"><meta name=twitter:image content="https://jimbobbennett.dev/blogs/ios-pull-to-refresh-without-using-a-uitableview/banner.png"><meta name=twitter:title content="iOS pull to refresh without using a UITableView"><meta name=twitter:description content="I’ve been working on the UI for my current app, trying to make some usability improvements. The main screen is a list of data containing an image and some text. Although this is a standard UI pattern, the problem I’m having with it is one of size. List rows are usually short and full width. For text this is fine, but not so good for images, then end up being small and not very easy to see."><meta name=twitter:site content="@jimbobbennett"><meta name=twitter:creator content="@jimbobbennett"><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css integrity=sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3 crossorigin=anonymous><link rel=stylesheet href=/css/header.css media=all><link rel=stylesheet href=/css/footer.css media=all><link rel=stylesheet href=/css/theme.css media=all><link rel="shortcut icon" type=image/png href=/fav.png><link rel="shortcut icon" sizes=192x192 href=/fav.png><link rel=apple-touch-icon href=/fav.png><link rel=alternate type=application/rss+xml href=https://jimbobbennett.dev/index.xml title=JimBobBennett><script type=text/javascript>(function(e,t,n,s,o,i,a){e[n]=e[n]||function(){(e[n].q=e[n].q||[]).push(arguments)},i=t.createElement(s),i.async=1,i.src="https://www.clarity.ms/tag/"+o,a=t.getElementsByTagName(s)[0],a.parentNode.insertBefore(i,a)})(window,document,"clarity","script","dctc2ydykv")</script><style>:root{--text-color:#343a40;--text-secondary-color:#6c757d;--background-color:#000;--secondary-background-color:#64ffda1a;--primary-color:#007bff;--secondary-color:#f8f9fa;--text-color-dark:#e4e6eb;--text-secondary-color-dark:#b0b3b8;--background-color-dark:#000000;--secondary-background-color-dark:#212529;--primary-color-dark:#ffffff;--secondary-color-dark:#212529}body{background-color:#000;font-size:1rem;font-weight:400;line-height:1.5;text-align:left}</style><meta name=description content><link rel=stylesheet href=/css/index.css><link rel=stylesheet href=/css/single.css><link rel=stylesheet href=/css/projects.css media=all><script defer src=/fontawesome-5/all-5.15.4.js></script><title>iOS pull to refresh without using a UITableView | JimBobBennett</title></head><body class=light onload=loading()><header><nav class="pt-3 navbar navbar-expand-lg"><div class="container-fluid mx-xs-2 mx-sm-5 mx-md-5 mx-lg-5"><a class="navbar-brand primary-font text-wrap" href=/><img src=/fav.png width=30 height=30 class="d-inline-block align-top">
|
|
JimBobBennett</a>
|
|
<button class=navbar-toggler type=button data-bs-toggle=collapse data-bs-target=#navbarContent aria-controls=navbarContent aria-expanded=false aria-label="Toggle navigation"><svg aria-hidden="true" height="24" viewBox="0 0 16 16" width="24" data-view-component="true"><path fill-rule="evenodd" d="M1 2.75A.75.75.0 011.75 2h12.5a.75.75.0 110 1.5H1.75A.75.75.0 011 2.75zm0 5A.75.75.0 011.75 7h12.5a.75.75.0 110 1.5H1.75A.75.75.0 011 7.75zM1.75 12a.75.75.0 100 1.5h12.5a.75.75.0 100-1.5H1.75z"/></svg></button><div class="collapse navbar-collapse text-wrap primary-font" id=navbarContent><ul class="navbar-nav ms-auto text-center"><li class="nav-item navbar-text"><a class=nav-link href=/ aria-label=home>Home</a></li><li class="nav-item navbar-text"><a class=nav-link href=/#about aria-label=about>About</a></li><li class="nav-item navbar-text"><a class=nav-link href=/#projects aria-label=projects>Recent Highlights</a></li><li class="nav-item navbar-text"><a class=nav-link href=/blogs title="Blog posts">Blog</a></li><li class="nav-item navbar-text"><a class=nav-link href=/videos title=Videos>Videos</a></li><li class="nav-item navbar-text"><a class=nav-link href=/livestreams title=Livestreams>Livestreams</a></li><li class="nav-item navbar-text"><a class=nav-link href=/conferences title=Conferences>Conferences</a></li><li class="nav-item navbar-text"><a class=nav-link href=/resume title=Resume>Resume</a></li></ul></div></div></nav></header><div id=content><section id=projects><div class="container pt-5" id=list-page><div class="row justify-content-center px-3 px-md-5"><h1 class="text-left pb-2 content">iOS pull to refresh without using a UITableView</h1><div class="text-left content">Jim Bennett
|
|
<small>|</small>
|
|
Sep 28, 2014</div></div></div></section><section id=single><div class=container><div class="row justify-content-center"><div class="col-sm-12 col-md-12 col-lg-9"><div class=pr-lg-4><article class="page-content p-2"><p>I’ve been working on the UI for my current app, trying to make some usability improvements. The main screen is a list of data containing an image and some text. Although this is a standard UI pattern, the problem I’m having with it is one of size. List rows are usually short and full width. For text this is fine, but not so good for images, then end up being small and not very easy to see. For the data I’m showing the image is just as important as the text, if not more so for quick identification of the data. I’s also wasting a lot of space as there number of rows will be very small most of the time, probably only one or 2 rows.
|
|
To improve this I’ve decided to move to a grid like layout, with varying columns depending on if the view is portrait or landscape. The end result is much nicer to look at and use, but coding it up let to a problem.</p><p>I was using a derivative of the Xamarin.Forms <code>ListView</code> class with support for pull to refresh courtesy of <a href=http://motzcod.es/post/87917979362/pull-to-refresh-for-xamarin-forms-ios>James Montemagno’s blog</a>. This provides a Xamarin wrapper around the well known <code>UIRefreshControl</code> and tying it to a <code>UITableView</code>.
|
|
This is all great, until you change from a <code>ListView</code> to something else, in my case a custom generated grid view.</p><p>Luckily, <code>UIScrollView</code> also supports pull to refresh, just it’s not as well documented. It has to be a <code>UIScrollView</code> or derivative, not any other control as it needs the pull down bounce to trigger the refresh.</p><p>To implement it in my grid view I first created a <code>PullToRefreshScrollView</code> and associated renderer to host my grid.
|
|
The code for the view lives in my portable project and just defines some bindable properties for the command to execute when refreshing, a flag to turn on the refresh indicator and a refresh message. All lifted directly from James’s <a href=https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshListView/blob/master/PullToRefresh/PullToRefreshListView.cs>PullToRefreshListView</a>.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cs data-lang=cs><span style=display:flex><span><span style=color:#66d9ef>public</span> <span style=color:#66d9ef>class</span> <span style=color:#a6e22e>PullToRefreshScrollView</span> : ScrollView
|
|
</span></span><span style=display:flex><span>{
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>static</span> <span style=color:#66d9ef>readonly</span> BindableProperty IsRefreshingProperty =
|
|
</span></span><span style=display:flex><span> BindableProperty.Create<PullToRefreshScrollView, <span style=color:#66d9ef>bool</span>>(p => p.IsRefreshing, <span style=color:#66d9ef>false</span>);
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>static</span> <span style=color:#66d9ef>readonly</span> BindableProperty RefreshCommandProperty =
|
|
</span></span><span style=display:flex><span> BindableProperty.Create<PullToRefreshScrollView, ICommand>(p => p.RefreshCommand, <span style=color:#66d9ef>null</span>);
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>static</span> <span style=color:#66d9ef>readonly</span> BindableProperty MessageProperty =
|
|
</span></span><span style=display:flex><span> BindableProperty.Create<PullToRefreshScrollView, <span style=color:#66d9ef>string</span>>(p => p.Message, <span style=color:#66d9ef>string</span>.Empty);
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>bool</span> IsRefreshing
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>get</span> { <span style=color:#66d9ef>return</span> (<span style=color:#66d9ef>bool</span>)GetValue(IsRefreshingProperty); }
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>set</span> { SetValue(IsRefreshingProperty, <span style=color:#66d9ef>value</span>); }
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> ICommand RefreshCommand
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>get</span> { <span style=color:#66d9ef>return</span> (ICommand)GetValue(RefreshCommandProperty); }
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>set</span> { SetValue(RefreshCommandProperty, <span style=color:#66d9ef>value</span>); }
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>string</span> Message
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>get</span> { <span style=color:#66d9ef>return</span> (<span style=color:#66d9ef>string</span>)GetValue(MessageProperty); }
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>set</span> { SetValue(MessageProperty, <span style=color:#66d9ef>value</span>); }
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>}
|
|
</span></span></code></pre></div><p>The next bit is a wrapper for the <code>UIRefreshControl</code> - again lifted from the <a href=https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshListView/blob/master/iOS/Renderers/FormsUIRefreshControl.cs>same place</a> and put into my iOS project.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cs data-lang=cs><span style=display:flex><span><span style=color:#66d9ef>public</span> <span style=color:#66d9ef>class</span> <span style=color:#a6e22e>FormsUIRefreshControl</span> : UIRefreshControl
|
|
</span></span><span style=display:flex><span>{
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> FormsUIRefreshControl()
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> ValueChanged += (sender, e) =>
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>var</span> command = RefreshCommand;
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span>(command == <span style=color:#66d9ef>null</span>)
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span>;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> command.Execute(<span style=color:#66d9ef>null</span>);
|
|
</span></span><span style=display:flex><span> };
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>private</span> <span style=color:#66d9ef>string</span> _message;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>string</span> Message
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>get</span> { <span style=color:#66d9ef>return</span> _message;}
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>set</span>
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> _message = <span style=color:#66d9ef>value</span>;
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> (<span style=color:#66d9ef>string</span>.IsNullOrWhiteSpace (_message))
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span>;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> AttributedTitle = <span style=color:#66d9ef>new</span> MonoTouch.Foundation.NSAttributedString(_message);
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>private</span> <span style=color:#66d9ef>bool</span> _isRefreshing;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> <span style=color:#66d9ef>bool</span> IsRefreshing
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>get</span> { <span style=color:#66d9ef>return</span> _isRefreshing;}
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>set</span>
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> _isRefreshing = <span style=color:#66d9ef>value</span>;
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> (_isRefreshing)
|
|
</span></span><span style=display:flex><span> BeginRefreshing();
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>else</span>
|
|
</span></span><span style=display:flex><span> EndRefreshing();
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>public</span> ICommand RefreshCommand { <span style=color:#66d9ef>get</span>; <span style=color:#66d9ef>set</span>; }
|
|
</span></span><span style=display:flex><span>}
|
|
</span></span></code></pre></div><p>Finally the renderer. To create the refresh control in the scroll view, it’s a simple case of creating the <code>UIRefreshControl</code> and adding it as a subview of the <code>UIScrollView</code>. When you pull the scroll view down it will trigger a ValueChange event on the <code>UIRefreshControl</code>, which our <code>FormsUIRefreshControl</code> handles to execute the provided command.
|
|
Now for the gotcha - the refresh only happens when you pull the scroll view down far enough. If the contents of the scroll view is smaller than the available space then it won’t scroll in either direction, stopping the pull action from doing anything. Good news is we can make the scroll view always scroll by setting:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cs data-lang=cs><span style=display:flex><span>AlwaysBounceVertical = <span style=color:#66d9ef>true</span>;
|
|
</span></span></code></pre></div><p>Here’s the full code for the renderer:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cs data-lang=cs><span style=display:flex><span><span style=color:#66d9ef>public</span> <span style=color:#66d9ef>class</span> <span style=color:#a6e22e>PullToRefreshScrollViewRenderer</span> : ScrollViewRenderer
|
|
</span></span><span style=display:flex><span>{
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>private</span> FormsUIRefreshControl _refreshControl;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>protected</span> <span style=color:#66d9ef>override</span> <span style=color:#66d9ef>void</span> OnElementChanged(VisualElementChangedEventArgs e)
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>base</span>.OnElementChanged(e);
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> (_refreshControl != <span style=color:#66d9ef>null</span>)
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span>;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>var</span> pullToRefreshScrollView = (PullToRefreshScrollView)Element;
|
|
</span></span><span style=display:flex><span> pullToRefreshScrollView.PropertyChanged += OnElementPropertyChanged;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> _refreshControl = <span style=color:#66d9ef>new</span> FormsUIRefreshControl
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> RefreshCommand = pullToRefreshScrollView.RefreshCommand,
|
|
</span></span><span style=display:flex><span> Message = pullToRefreshScrollView.Message
|
|
</span></span><span style=display:flex><span> };
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> AlwaysBounceVertical = <span style=color:#66d9ef>true</span>;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> AddSubview(_refreshControl);
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>private</span> <span style=color:#66d9ef>void</span> OnElementPropertyChanged(<span style=color:#66d9ef>object</span> sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
</span></span><span style=display:flex><span> {
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>var</span> pullToRefreshScrollView = Element <span style=color:#66d9ef>as</span> PullToRefreshScrollView;
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> (pullToRefreshScrollView == <span style=color:#66d9ef>null</span>)
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span>;
|
|
</span></span><span style=display:flex><span>
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> (e.PropertyName == PullToRefreshScrollView.IsRefreshingProperty.PropertyName)
|
|
</span></span><span style=display:flex><span> _refreshControl.IsRefreshing = pullToRefreshScrollView.IsRefreshing;
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>else</span> <span style=color:#66d9ef>if</span> (e.PropertyName == PullToRefreshScrollView.MessageProperty.PropertyName)
|
|
</span></span><span style=display:flex><span> _refreshControl.Message = pullToRefreshScrollView.Message;
|
|
</span></span><span style=display:flex><span> <span style=color:#66d9ef>else</span> <span style=color:#66d9ef>if</span> (e.PropertyName == PullToRefreshScrollView.RefreshCommandProperty.PropertyName)
|
|
</span></span><span style=display:flex><span> _refreshControl.RefreshCommand = pullToRefreshScrollView.RefreshCommand;
|
|
</span></span><span style=display:flex><span> }
|
|
</span></span><span style=display:flex><span>}
|
|
</span></span></code></pre></div><p>This control is now part of <a href=Xamarin>JimLib.Xamarin, available on GitHub</a>.</p></article></div></div><div class="col-sm-12 col-md-12 col-lg-3"><div class=sticky-sidebar><aside class=toc><h5>Table Of Contents</h5><div class=toc-content><nav id=TableOfContents></nav></div></aside><aside class=tags><h5>Tags</h5><ul class="tags-ul list-unstyled list-inline"><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/technology target=_blank>Technology</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/xamarin target=_blank>xamarin</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/xamarin.ios target=_blank>xamarin.ios</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/technology target=_blank>technology</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/xamarin.forms target=_blank>xamarin.forms</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/jimlib.xamarin target=_blank>jimlib.xamarin</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/pull-to-refresh target=_blank>pull to refresh</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/scrollview target=_blank>scrollview</a></li></ul></aside></div></div></div><div class=row><div class="col-sm-12 col-md-12 col-lg-9 p-4"><div id=disqus_thread></div><script>var disqus_config=function(){this.page.url="https://jimbobbennett.dev/blogs/ios-pull-to-refresh-without-using-a-uitableview/",this.page.identifier="968a58776f78d876246288bb32d2a7b7"};(function(){if(window.location.hostname=="localhost")return;var e=document,t=e.createElement("script");t.src="https://jimbobbennett.disqus.com/embed.js",t.setAttribute("data-timestamp",+new Date),(e.head||e.body).appendChild(t)})()</script><noscript>Please enable JavaScript to view the <a href=https://disqus.com/?ref_noscript>comments powered by Disqus.</a></noscript></div></div></div><button class="p-2 px-3" onclick=topFunction() id=topScroll>
|
|
<i class="fas fa-angle-up"></i></button></section><script>var topScroll=document.getElementById("topScroll");window.onscroll=function(){scrollFunction()};function scrollFunction(){document.body.scrollTop>20||document.documentElement.scrollTop>20?topScroll.style.display="block":topScroll.style.display="none"}function topFunction(){document.body.scrollTop=0,document.documentElement.scrollTop=0}</script></div><footer><div class="container py-4"><div class="row justify-content-center"><div class="col-md-4 text-center">© 2022 All Rights Reserved</div></div></div></div></footer><script src=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js integrity=sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13 crossorigin=anonymous></script>
|
|
<script>document.body.className.includes("light")&&(document.body.classList.add("dark"),localStorage.setItem("pref-theme","dark"))</script><script>let loadingIcons;function loading(){myVar=setTimeout(showPage,100)}function showPage(){try{document.getElementById("loading-icons").style.display="block"}catch{}}</script><script>function createCopyButton(e,t){const n=document.createElement("button");n.className="copy-code-button",n.type="button",n.innerText="Copy",n.addEventListener("click",()=>copyCodeToClipboard(n,e,t)),addCopyButtonToDom(n,e)}async function copyCodeToClipboard(e,t,n){const s=t.querySelector("pre > code").innerText;try{n.writeText(s)}finally{codeWasCopied(e)}}function codeWasCopied(e){e.blur(),e.innerText="Copied!",setTimeout(function(){e.innerText="Copy"},2e3)}function addCopyButtonToDom(e,t){t.insertBefore(e,t.firstChild);const n=document.createElement("div");n.className="highlight-wrapper",t.parentNode.insertBefore(n,t),n.appendChild(t)}if(navigator&&navigator.clipboard)document.querySelectorAll(".highlight").forEach(e=>createCopyButton(e,navigator.clipboard));else{var script=document.createElement("script");script.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard-polyfill/2.7.0/clipboard-polyfill.promise.js",script.integrity="sha256-waClS2re9NUbXRsryKoof+F9qc1gjjIhc2eT7ZbIv94=",script.crossOrigin="anonymous",script.onload=function(){addCopyButtons(clipboard)},document.querySelectorAll(".highlight").forEach(e=>createCopyButton(e,script)),document.body.appendChild(script)}</script></body></html> |