├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── Droid ├── Assets │ └── AboutAssets.txt ├── MainActivity.cs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.cs ├── RefreshSample.Droid.csproj └── Resources │ ├── AboutResources.txt │ ├── Resource.designer.cs │ ├── drawable-hdpi │ └── icon.png │ ├── drawable-xhdpi │ └── icon.png │ ├── drawable-xxhdpi │ └── icon.png │ ├── drawable │ └── icon.png │ ├── values-v21 │ └── styles.xml │ └── values │ └── styles.xml ├── PullToRefresh └── PullToRefresh.Standard │ ├── PullToRefresh.csproj │ ├── PullToRefreshLayout.shared.cs │ ├── PullToRefreshLayoutRenderer.android.cs │ └── PullToRefreshLayoutRenderer.ios.cs ├── PullToRefreshLayout.nuspec ├── PullToRefreshLayout.sln ├── README.md ├── RefreshSample.userprefs ├── RefreshSample ├── App.cs ├── RefreshSample.csproj ├── ViewModels │ └── TestViewModel.cs └── Views │ ├── DisposeCrashPage.cs │ ├── GridPage.cs │ ├── ListViewPage.cs │ ├── ScrollViewPage.cs │ ├── ScrollViewPageManual.cs │ ├── ScrollViewXamlPage.xaml │ ├── ScrollViewXamlPage.xaml.cs │ ├── StackLayoutPage.cs │ ├── TabbedPage1.xaml │ └── TabbedPage1.xaml.cs ├── appveyor.yml ├── build.cake ├── build.ps1 ├── build.sh ├── cake.packages.config ├── demo.gif └── iOS ├── AppDelegate.cs ├── Entitlements.plist ├── Info.plist ├── Main.cs ├── RefreshSample.iOS.csproj └── Resources ├── Images.xcassets └── AppIcons.appiconset │ └── Contents.json └── LaunchScreen.xib /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: mergeconflictfm 2 | custom: https://www.buymeacoffee.com/jamesmontemagno 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | .vs/ 6 | 7 | *.pydevproject 8 | .project 9 | .metadata 10 | bin/ 11 | tmp/ 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .classpath 18 | .settings/ 19 | .loadpath 20 | .nuget/ 21 | 22 | # External tool builders 23 | .externalToolBuilders/ 24 | 25 | # Locally stored "Eclipse launch configurations" 26 | *.launch 27 | 28 | # CDT-specific 29 | .cproject 30 | 31 | # PDT-specific 32 | .buildpath 33 | 34 | 35 | ################# 36 | ## Visual Studio 37 | ################# 38 | 39 | ## Ignore Visual Studio temporary files, build results, and 40 | ## files generated by popular Visual Studio add-ons. 41 | 42 | # User-specific files 43 | *.suo 44 | *.user 45 | *.sln.docstates 46 | 47 | # Build results 48 | 49 | [Dd]ebug/ 50 | [Rr]elease/ 51 | x64/ 52 | build/ 53 | [Bb]in/ 54 | [Oo]bj/ 55 | [Pp]ackages/ 56 | 57 | # MSTest test Results 58 | [Tt]est[Rr]esult*/ 59 | [Bb]uild[Ll]og.* 60 | 61 | *_i.c 62 | *_p.c 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.pch 67 | *.pdb 68 | *.pgc 69 | *.pgd 70 | *.rsp 71 | *.sbr 72 | *.tlb 73 | *.tli 74 | *.tlh 75 | *.tmp 76 | *.tmp_proj 77 | *.log 78 | *.vspscc 79 | *.vssscc 80 | .builds 81 | *.pidb 82 | *.log 83 | *.scc 84 | 85 | # Visual C++ cache files 86 | ipch/ 87 | *.aps 88 | *.ncb 89 | *.opensdf 90 | *.sdf 91 | *.cachefile 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | *.ncrunch* 113 | .*crunch*.local.xml 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.Publish.xml 133 | *.pubxml 134 | 135 | # NuGet Packages Directory 136 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 137 | #packages/ 138 | 139 | # Windows Azure Build Output 140 | csx 141 | *.build.csdef 142 | 143 | # Windows Store app package directory 144 | AppPackages/ 145 | 146 | # Others 147 | sql/ 148 | *.Cache 149 | ClientBin/ 150 | [Ss]tyle[Cc]op.* 151 | ~$* 152 | *~ 153 | *.dbmdl 154 | *.[Pp]ublish.xml 155 | *.pfx 156 | *.publishsettings 157 | 158 | # RIA/Silverlight projects 159 | Generated_Code/ 160 | 161 | # Backup & report files from converting an old project file to a newer 162 | # Visual Studio version. Backup files are not needed, because we have git ;-) 163 | _UpgradeReport_Files/ 164 | Backup*/ 165 | UpgradeLog*.XML 166 | UpgradeLog*.htm 167 | 168 | # SQL Server files 169 | App_Data/*.mdf 170 | App_Data/*.ldf 171 | 172 | ############# 173 | ## Windows detritus 174 | ############# 175 | 176 | # Windows image file caches 177 | Thumbs.db 178 | ehthumbs.db 179 | 180 | # Folder config file 181 | Desktop.ini 182 | 183 | # Recycle Bin used on file shares 184 | $RECYCLE.BIN/ 185 | 186 | # Mac crap 187 | .DS_Store 188 | 189 | 190 | ############# 191 | ## Python 192 | ############# 193 | 194 | *.py[co] 195 | 196 | # Packages 197 | *.egg 198 | *.egg-info 199 | dist/ 200 | build/ 201 | eggs/ 202 | parts/ 203 | var/ 204 | sdist/ 205 | develop-eggs/ 206 | .installed.cfg 207 | 208 | # Installer logs 209 | pip-log.txt 210 | 211 | # Unit test / coverage reports 212 | .coverage 213 | .tox 214 | 215 | #Translations 216 | *.mo 217 | 218 | #Mr Developer 219 | .mr.developer.cfg 220 | tools/**/*.* 221 | .vs/PullToRefreshLayout/v15/sqlite3/storage.ide 222 | *.ide-journal 223 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.4.0 2 | * Multitarget and Azure DevOps 3 | 4 | ## 2.2.3 5 | * Fixes #45 Extra space on iOS 6 | * Fixes #37 Content Too Low 7 | * Fixes #27 Not whoswing when programmatically 8 | * Fixes #26 Missing RefreshCommandParameter 9 | * Fixes #3 iOS set to false 10 | * Update to newer Forms 11 | 12 | ## 2.0.1 13 | * Update Xamarin.Forms 14 | * .NET Standard Libary 15 | * Add command parameter #26 16 | * Fix for spinner now showing when setting IsRefreshing from code behind #27/#3 17 | -------------------------------------------------------------------------------- /Droid/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /Droid/MainActivity.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using Android.App; 16 | using Android.Content.PM; 17 | using Android.OS; 18 | using Android.Graphics.Drawables; 19 | using Refractored.XamForms.PullToRefresh.Droid; 20 | 21 | namespace RefreshSample.Droid 22 | { 23 | [Activity(Label = "Pull to Refresh!", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] 24 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity 25 | { 26 | protected override void OnCreate(Bundle bundle) 27 | { 28 | base.OnCreate(bundle); 29 | 30 | global::Xamarin.Forms.Forms.Init(this, bundle); 31 | 32 | PullToRefreshLayoutRenderer.Init(); 33 | 34 | LoadApplication(new App()); 35 | 36 | if((int)Build.VERSION.SdkInt >= 21) 37 | ActionBar.SetIcon ( new ColorDrawable (Resources.GetColor (Android.Resource.Color.Transparent))); 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Droid/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using Android.App; 4 | 5 | // Information about this assembly is defined by the following attributes. 6 | // Change them to the values specific to your project. 7 | 8 | [assembly: AssemblyTitle("RefreshSample.Droid")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("jamesmontemagno")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 20 | 21 | [assembly: AssemblyVersion("1.0.1")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | 29 | -------------------------------------------------------------------------------- /Droid/RefreshSample.Droid.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 7 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B} 8 | Library 9 | RefreshSample.Droid 10 | Assets 11 | Resources 12 | Resource 13 | Resources\Resource.designer.cs 14 | True 15 | RefreshSample.Droid 16 | Properties\AndroidManifest.xml 17 | v9.0 18 | 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug 26 | DEBUG; 27 | prompt 28 | 4 29 | None 30 | false 31 | 32 | 33 | full 34 | true 35 | bin\Release 36 | prompt 37 | 4 38 | false 39 | false 40 | armeabi-v7a;x86 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {10ab134c-d1b9-4589-8121-3ceb3d21d096} 51 | PullToRefresh 52 | 53 | 54 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27} 55 | RefreshSample 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 28.0.0.3 82 | 83 | 84 | 28.0.0.3 85 | 86 | 87 | 28.0.0.3 88 | 89 | 90 | 28.0.0.3 91 | 92 | 93 | 0.4.11 94 | 95 | 96 | 4.3.0.991211 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Droid/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.axml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable/ 12 | icon.png 13 | 14 | layout/ 15 | main.axml 16 | 17 | values/ 18 | strings.xml 19 | 20 | In order to get the build system to recognize Android resources, set the build action to 21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 22 | instead operate on resource IDs. When you compile an Android application that uses resources, 23 | the build system will package the resources for distribution and generate a class called "R" 24 | (this is an Android convention) that contains the tokens for each one of the resources 25 | included. For example, for the above Resources layout, this is what the R class would expose: 26 | 27 | public class R { 28 | public class drawable { 29 | public const int icon = 0x123; 30 | } 31 | 32 | public class layout { 33 | public const int main = 0x456; 34 | } 35 | 36 | public class strings { 37 | public const int first_string = 0xabc; 38 | public const int second_string = 0xbcd; 39 | } 40 | } 41 | 42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main 43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first 44 | string in the dictionary file values/strings.xml. 45 | -------------------------------------------------------------------------------- /Droid/Resources/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout/661faad38ad10bc7f8d5774c99cf989356fcbd4e/Droid/Resources/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout/661faad38ad10bc7f8d5774c99cf989356fcbd4e/Droid/Resources/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout/661faad38ad10bc7f8d5774c99cf989356fcbd4e/Droid/Resources/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout/661faad38ad10bc7f8d5774c99cf989356fcbd4e/Droid/Resources/drawable/icon.png -------------------------------------------------------------------------------- /Droid/Resources/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 14 | -------------------------------------------------------------------------------- /Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | #3498db 3 | #2c3e50 4 | #FFC107 5 | 6 | 10 | 11 | 14 | 15 | 19 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /PullToRefresh/PullToRefresh.Standard/PullToRefresh.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard1.0;netstandard2.0;MonoAndroid81;MonoAndroid90;Xamarin.iOS10 4 | Refractored.XamForms.PullToRefresh 5 | Refractored.XamForms.PullToRefresh 6 | $(AssemblyName) ($(TargetFramework)) 7 | 1.0.0.0 8 | 1.0.0.0 9 | 1.0.0.0 10 | James Montemagno 11 | Refractored.XamForms.PullToRefresh 12 | true 13 | http://www.refractored.com/images/pull_to_refresh.png 14 | https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout 15 | JamesMontemagno 16 | https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout 17 | Enhanced Pull To Refresh Layout for Xamarin.Forms 18 | xamarin, pcl, xam.pcl, android, xamarin.forms, ios, pull to refresh, refresh, swipe 19 | PullToRefresh Layout for Xamarin.Forms 20 | 21 | Add Pull to Refresh to ScrollView or group a ListView. See Project for more details 22 | Ensure you call PullToRefreshLayoutRenderer.Init() on each platform! 23 | Built against: 4.2.0.709249 24 | 25 | Copyright 2019 26 | https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout 27 | See: https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout 28 | 29 | en 30 | default 31 | false 32 | 33 | $(DefineConstants); 34 | 35 | 36 | 37 | full 38 | true 39 | 40 | 41 | pdbonly 42 | true 43 | true 44 | 45 | 46 | portable 47 | 48 | true 49 | 50 | true 51 | 52 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /PullToRefresh/PullToRefresh.Standard/PullToRefreshLayout.shared.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.Windows.Input; 18 | using Xamarin.Forms; 19 | 20 | namespace Refractored.XamForms.PullToRefresh 21 | { 22 | /// 23 | /// Pull to refresh layout. 24 | /// 25 | public class PullToRefreshLayout : ContentView 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public PullToRefreshLayout() 31 | { 32 | IsClippedToBounds = true; 33 | VerticalOptions = LayoutOptions.FillAndExpand; 34 | HorizontalOptions = LayoutOptions.FillAndExpand; 35 | } 36 | 37 | /// 38 | /// The is refreshing property. 39 | /// 40 | public static readonly BindableProperty IsRefreshingProperty = 41 | BindableProperty.Create(nameof(IsRefreshing), typeof(bool), typeof(PullToRefreshLayout), false); 42 | 43 | /// 44 | /// Gets or sets a value indicating whether this instance is refreshing. 45 | /// 46 | /// true if this instance is refreshing; otherwise, false. 47 | public bool IsRefreshing 48 | { 49 | get { return (bool)GetValue(IsRefreshingProperty); } 50 | set 51 | { 52 | if ((bool)GetValue(IsRefreshingProperty) == value) 53 | OnPropertyChanged(nameof(IsRefreshing)); 54 | 55 | SetValue(IsRefreshingProperty, value); 56 | } 57 | } 58 | 59 | /// 60 | /// The is pull to refresh enabled property. 61 | /// 62 | public static readonly BindableProperty IsPullToRefreshEnabledProperty = 63 | BindableProperty.Create(nameof(IsPullToRefreshEnabled), typeof(bool), typeof(PullToRefreshLayout), true); 64 | 65 | /// 66 | /// Gets or sets a value indicating whether this instance is pull to refresh enabled. 67 | /// 68 | /// true if this instance is pull to refresh enabled; otherwise, false. 69 | public bool IsPullToRefreshEnabled 70 | { 71 | get { return (bool)GetValue(IsPullToRefreshEnabledProperty); } 72 | set { SetValue(IsPullToRefreshEnabledProperty, value); } 73 | } 74 | 75 | 76 | /// 77 | /// The refresh command property. 78 | /// 79 | public static readonly BindableProperty RefreshCommandProperty = 80 | BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(PullToRefreshLayout)); 81 | 82 | /// 83 | /// Gets or sets the refresh command. 84 | /// 85 | /// The refresh command. 86 | public ICommand RefreshCommand 87 | { 88 | get { return (ICommand)GetValue(RefreshCommandProperty); } 89 | set { SetValue(RefreshCommandProperty, value); } 90 | } 91 | 92 | /// 93 | /// Gets the Refresh command 94 | /// 95 | public static readonly BindableProperty RefreshCommandParameterProperty = 96 | BindableProperty.Create(nameof(RefreshCommandParameter), 97 | typeof(object), 98 | typeof(PullToRefreshLayout), 99 | null, 100 | propertyChanged: (bindable, oldvalue, newvalue) => ((PullToRefreshLayout)bindable).RefreshCommandCanExecuteChanged(bindable, EventArgs.Empty)); 101 | 102 | /// 103 | /// Gets or sets the Refresh command parameter 104 | /// 105 | public object RefreshCommandParameter 106 | { 107 | get { return GetValue(RefreshCommandParameterProperty); } 108 | set { SetValue(RefreshCommandParameterProperty, value); } 109 | } 110 | 111 | /// 112 | /// Executes if enabled or not based on can execute changed 113 | /// 114 | /// 115 | /// 116 | void RefreshCommandCanExecuteChanged(object sender, EventArgs eventArgs) 117 | { 118 | ICommand cmd = RefreshCommand; 119 | if (cmd != null) 120 | IsEnabled = cmd.CanExecute(RefreshCommandParameter); 121 | } 122 | 123 | /// 124 | /// Color property of refresh spinner color 125 | /// 126 | public static readonly BindableProperty RefreshColorProperty = 127 | BindableProperty.Create(nameof(RefreshColor), typeof(Color), typeof(PullToRefreshLayout), Color.Default); 128 | 129 | /// 130 | /// Refresh color 131 | /// 132 | public Color RefreshColor 133 | { 134 | get { return (Color)GetValue(RefreshColorProperty); } 135 | set { SetValue(RefreshColorProperty, value); } 136 | } 137 | 138 | 139 | 140 | /// 141 | /// Color property of refresh background color 142 | /// 143 | public static readonly BindableProperty RefreshBackgroundColorProperty = 144 | BindableProperty.Create(nameof(RefreshBackgroundColor), typeof(Color), typeof(PullToRefreshLayout), Color.Default); 145 | 146 | /// 147 | /// Refresh background color 148 | /// 149 | public Color RefreshBackgroundColor 150 | { 151 | get { return (Color)GetValue(RefreshBackgroundColorProperty); } 152 | set { SetValue(RefreshBackgroundColorProperty, value); } 153 | } 154 | 155 | 156 | /// 157 | /// The Is Intercept Horizontal Scroll property. 158 | /// 159 | public static readonly BindableProperty IsInterceptHorizontalScrollProperty = 160 | BindableProperty.Create(nameof(IsInterceptHorizontalScroll), typeof(bool), typeof(PullToRefreshLayout), true); 161 | 162 | /// 163 | /// Gets or sets a value indicating whether the pull-to-refresh behaviour will intercept the inner horizontal scroll. 164 | /// By default, this value is true 165 | /// 166 | /// true if the pull-to-refresh will intercept horizontal scroll; otherwise, false. 167 | public bool IsInterceptHorizontalScroll 168 | { 169 | get { return (bool)GetValue(IsInterceptHorizontalScrollProperty); } 170 | set { SetValue(IsInterceptHorizontalScrollProperty, value); } 171 | } 172 | 173 | 174 | /// The available width for the element to use. 175 | /// The available height for the element to use. 176 | /// 177 | /// Optimization as we can get the size here of our content all in DIP 178 | /// 179 | protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) 180 | { 181 | if (Content == null) 182 | return new SizeRequest(new Size(100, 100)); 183 | 184 | return base.OnMeasure(widthConstraint, heightConstraint); 185 | } 186 | } 187 | } 188 | 189 | -------------------------------------------------------------------------------- /PullToRefresh/PullToRefresh.Standard/PullToRefreshLayoutRenderer.android.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using System.ComponentModel; 17 | using System.Reflection; 18 | using Android.Runtime; 19 | using Android.Support.V4.Widget; 20 | using Android.Views; 21 | using Refractored.XamForms.PullToRefresh; 22 | using Refractored.XamForms.PullToRefresh.Droid; 23 | using Xamarin.Forms; 24 | using Xamarin.Forms.Platform.Android; 25 | 26 | 27 | [assembly: ExportRenderer(typeof(PullToRefreshLayout), typeof(PullToRefreshLayoutRenderer))] 28 | namespace Refractored.XamForms.PullToRefresh.Droid 29 | { 30 | /// 31 | /// Pull to refresh layout renderer. 32 | /// 33 | [Preserve(AllMembers = true)] 34 | public class PullToRefreshLayoutRenderer : SwipeRefreshLayout, 35 | IVisualElementRenderer, 36 | SwipeRefreshLayout.IOnRefreshListener 37 | { 38 | /// 39 | /// Used for registration with dependency service 40 | /// 41 | public async static void Init() 42 | { 43 | var temp = DateTime.Now; 44 | } 45 | 46 | /// 47 | /// Initializes a new instance of the 48 | /// class. 49 | /// 50 | public PullToRefreshLayoutRenderer(Android.Content.Context context) 51 | : base(context) 52 | { 53 | touchSlop = ViewConfiguration.Get(context).ScaledTouchSlop; 54 | } 55 | 56 | /// 57 | /// Occurs when element changed. 58 | /// 59 | public event EventHandler ElementChanged; 60 | public event EventHandler ElementPropertyChanged; 61 | 62 | bool init; 63 | IVisualElementRenderer packed; 64 | /// 65 | /// Setup our SwipeRefreshLayout and register for property changed notifications. 66 | /// 67 | /// Element. 68 | public void SetElement(VisualElement element) 69 | { 70 | var oldElement = Element; 71 | 72 | //unregister old and re-register new 73 | if (oldElement != null) 74 | oldElement.PropertyChanged -= HandlePropertyChanged; 75 | 76 | Element = element; 77 | if (Element != null) 78 | { 79 | UpdateContent(); 80 | Element.PropertyChanged += HandlePropertyChanged; 81 | } 82 | 83 | if (!init) 84 | { 85 | init = true; 86 | //sizes to match the forms view 87 | //updates properties, handles visual element properties 88 | Tracker = new VisualElementTracker(this); 89 | SetOnRefreshListener(this); 90 | } 91 | 92 | UpdateColors(); 93 | UpdateIsRefreshing(); 94 | UpdateIsSwipeToRefreshEnabled(); 95 | UpdateIsInterceptHorizontalScroll(); 96 | 97 | if (ElementChanged != null) 98 | ElementChanged(this, new VisualElementChangedEventArgs(oldElement, this.Element)); 99 | } 100 | 101 | /// 102 | /// Managest adding and removing the android viewgroup to our actual swiperefreshlayout 103 | /// 104 | void UpdateContent() 105 | { 106 | if (RefreshView.Content == null) 107 | return; 108 | 109 | if (packed != null) 110 | RemoveView(packed.View); 111 | 112 | packed = Platform.CreateRendererWithContext(RefreshView.Content, Context); 113 | 114 | try 115 | { 116 | RefreshView.Content.SetValue(RendererProperty, packed); 117 | } 118 | catch (Exception ex) 119 | { 120 | System.Diagnostics.Debug.WriteLine("Unable to sent renderer property, maybe an issue: " + ex); 121 | } 122 | 123 | AddView(packed.View, LayoutParams.MatchParent); 124 | 125 | } 126 | 127 | BindableProperty rendererProperty = null; 128 | 129 | /// 130 | /// Gets the bindable property. 131 | /// 132 | /// The bindable property. 133 | BindableProperty RendererProperty 134 | { 135 | get 136 | { 137 | if (rendererProperty != null) 138 | return rendererProperty; 139 | 140 | var type = Type.GetType("Xamarin.Forms.Platform.Android.Platform, Xamarin.Forms.Platform.Android"); 141 | var prop = type.GetField("RendererProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); 142 | var val = prop.GetValue(null); 143 | rendererProperty = val as BindableProperty; 144 | 145 | return rendererProperty; 146 | } 147 | } 148 | 149 | void UpdateColors() 150 | { 151 | if (RefreshView == null) 152 | return; 153 | if (RefreshView.RefreshColor != Color.Default) 154 | SetColorSchemeColors(RefreshView.RefreshColor.ToAndroid()); 155 | if (RefreshView.RefreshBackgroundColor != Color.Default) 156 | SetProgressBackgroundColorSchemeColor(RefreshView.RefreshBackgroundColor.ToAndroid()); 157 | } 158 | 159 | bool refreshing; 160 | /// 161 | /// Gets or sets a value indicating whether this 162 | /// is refreshing. 163 | /// 164 | /// true if refreshing; otherwise, false. 165 | public override bool Refreshing 166 | { 167 | get 168 | { 169 | return refreshing; 170 | } 171 | set 172 | { 173 | try 174 | { 175 | refreshing = value; 176 | //this will break binding :( sad panda we need to wait for next version for this 177 | //right now you can't update the binding.. so it is 1 way 178 | if (RefreshView != null && RefreshView.IsRefreshing != refreshing) 179 | RefreshView.IsRefreshing = refreshing; 180 | 181 | if (base.Refreshing == refreshing) 182 | return; 183 | 184 | base.Refreshing = refreshing; 185 | } 186 | catch (Exception ex) 187 | { 188 | } 189 | } 190 | } 191 | 192 | void UpdateIsRefreshing() => 193 | Refreshing = RefreshView.IsRefreshing; 194 | 195 | 196 | void UpdateIsSwipeToRefreshEnabled() => 197 | Enabled = RefreshView.IsPullToRefreshEnabled; 198 | 199 | 200 | 201 | /// 202 | /// Determines whether this instance can child scroll up. 203 | /// We do this since the actual swipe refresh can't figure it out 204 | /// 205 | /// true if this instance can child scroll up; otherwise, false. 206 | public override bool CanChildScrollUp() => 207 | CanScrollUp(packed.View); 208 | 209 | 210 | bool CanScrollUp(Android.Views.View view) 211 | { 212 | var viewGroup = view as ViewGroup; 213 | if (viewGroup == null) 214 | return base.CanChildScrollUp(); 215 | 216 | var sdk = (int)global::Android.OS.Build.VERSION.SdkInt; 217 | if (sdk >= 16) 218 | { 219 | //is a scroll container such as listview, scroll view, gridview 220 | if (viewGroup.IsScrollContainer) 221 | { 222 | return base.CanChildScrollUp(); 223 | } 224 | } 225 | 226 | //if you have something custom and you can't scroll up you might need to enable this 227 | //for instance on a custom recycler view where the code above isn't working! 228 | for (int i = 0; i < viewGroup.ChildCount; i++) 229 | { 230 | var child = viewGroup.GetChildAt(i); 231 | if (child is Android.Widget.AbsListView) 232 | { 233 | var list = child as Android.Widget.AbsListView; 234 | if (list != null) 235 | { 236 | if (list.FirstVisiblePosition == 0) 237 | { 238 | var subChild = list.GetChildAt(0); 239 | 240 | return subChild != null && subChild.Top != 0; 241 | } 242 | 243 | //if children are in list and we are scrolled a bit... sure you can scroll up 244 | return true; 245 | } 246 | 247 | } 248 | else if (child is Android.Widget.ScrollView) 249 | { 250 | var scrollview = child as Android.Widget.ScrollView; 251 | return (scrollview.ScrollY <= 0.0); 252 | } 253 | else if (child is Android.Webkit.WebView) 254 | { 255 | var webView = child as Android.Webkit.WebView; 256 | return (webView.ScrollY > 0.0); 257 | } 258 | else if (child is Android.Support.V4.Widget.SwipeRefreshLayout) 259 | { 260 | return CanScrollUp(child as ViewGroup); 261 | } 262 | //else if something else like a recycler view? 263 | 264 | } 265 | 266 | return false; 267 | } 268 | 269 | 270 | /// 271 | /// Helpers to cast our element easily 272 | /// Will throw an exception if the Element is not correct 273 | /// 274 | /// The refresh view. 275 | public Refractored.XamForms.PullToRefresh.PullToRefreshLayout RefreshView => 276 | Element as PullToRefreshLayout; 277 | 278 | /// 279 | /// The refresh view has been refreshed 280 | /// 281 | public void OnRefresh() 282 | { 283 | if (RefreshView?.RefreshCommand?.CanExecute(RefreshView?.RefreshCommandParameter) ?? false) 284 | { 285 | RefreshView.RefreshCommand.Execute(RefreshView?.RefreshCommandParameter); 286 | } 287 | } 288 | 289 | 290 | /// 291 | /// Handles the property changed. 292 | /// Update the control and trigger refreshing 293 | /// 294 | /// Sender. 295 | /// E. 296 | void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) 297 | { 298 | if (isDisposed) 299 | return; 300 | if (e.PropertyName == "Content") 301 | UpdateContent(); 302 | else if (e.PropertyName == PullToRefreshLayout.IsPullToRefreshEnabledProperty.PropertyName) 303 | UpdateIsSwipeToRefreshEnabled(); 304 | else if (e.PropertyName == PullToRefreshLayout.IsRefreshingProperty.PropertyName) 305 | UpdateIsRefreshing(); 306 | else if (e.PropertyName == PullToRefreshLayout.RefreshColorProperty.PropertyName) 307 | UpdateColors(); 308 | else if (e.PropertyName == PullToRefreshLayout.RefreshBackgroundColorProperty.PropertyName) 309 | UpdateColors(); 310 | else if (e.PropertyName == PullToRefreshLayout.IsInterceptHorizontalScrollProperty.PropertyName) 311 | UpdateIsInterceptHorizontalScroll(); 312 | } 313 | 314 | /// 315 | /// Gets the size of the desired. 316 | /// 317 | /// The desired size. 318 | /// Width constraint. 319 | /// Height constraint. 320 | public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint) 321 | { 322 | packed.View.Measure(widthConstraint, heightConstraint); 323 | 324 | //Measure child here and determine size 325 | return new SizeRequest(new Size(packed.View.MeasuredWidth, packed.View.MeasuredHeight)); 326 | } 327 | 328 | /// 329 | /// Updates the layout. 330 | /// 331 | public void UpdateLayout() => Tracker?.UpdateLayout(); 332 | 333 | 334 | /// 335 | /// Gets the tracker. 336 | /// 337 | /// The tracker. 338 | public VisualElementTracker Tracker { get; private set; } 339 | 340 | 341 | /// 342 | /// Gets the view group. 343 | /// 344 | /// The view group. 345 | public Android.Views.ViewGroup ViewGroup => this; 346 | 347 | 348 | public Android.Views.View View => this; 349 | 350 | /// 351 | /// Gets the element. 352 | /// 353 | /// The element. 354 | public VisualElement Element { get; private set; } 355 | 356 | bool isDisposed = false; 357 | /// 358 | /// Cleanup layout. 359 | /// 360 | /// 361 | protected override void Dispose(bool disposing) 362 | { 363 | base.Dispose(disposing); 364 | isDisposed = true; 365 | 366 | if (disposing) 367 | { 368 | if (Element != null) 369 | { 370 | Element.PropertyChanged -= HandlePropertyChanged; 371 | } 372 | 373 | if (packed != null) 374 | RemoveView(packed.View); 375 | } 376 | 377 | packed?.Dispose(); 378 | packed = null; 379 | 380 | Tracker?.Dispose(); 381 | Tracker = null; 382 | 383 | 384 | if (rendererProperty != null) 385 | { 386 | rendererProperty = null; 387 | } 388 | init = false; 389 | } 390 | 391 | void UpdateIsInterceptHorizontalScroll() 392 | { 393 | if (RefreshView == null) 394 | return; 395 | 396 | isInterceptHorizontalScroll = RefreshView.IsInterceptHorizontalScroll; 397 | } 398 | 399 | private bool isInterceptHorizontalScroll; 400 | private int touchSlop; 401 | private float previousX; 402 | public override bool OnInterceptTouchEvent(MotionEvent ev) 403 | { 404 | if (isInterceptHorizontalScroll) 405 | return base.OnInterceptTouchEvent(ev); 406 | 407 | switch (ev.Action) 408 | { 409 | case MotionEventActions.Down: 410 | previousX = MotionEvent.Obtain(ev).GetX(); 411 | break; 412 | 413 | case MotionEventActions.Move: 414 | float eventX = ev.GetX(); 415 | float xDiff = Math.Abs(eventX - previousX); 416 | 417 | if (xDiff > touchSlop) 418 | { 419 | return false; 420 | } 421 | break; 422 | } 423 | 424 | return base.OnInterceptTouchEvent(ev); 425 | } 426 | 427 | public void SetLabelFor(int? id) 428 | { 429 | 430 | } 431 | } 432 | } 433 | 434 | -------------------------------------------------------------------------------- /PullToRefresh/PullToRefresh.Standard/PullToRefreshLayoutRenderer.ios.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using System.ComponentModel; 17 | using Foundation; 18 | using Refractored.XamForms.PullToRefresh; 19 | using Refractored.XamForms.PullToRefresh.iOS; 20 | using UIKit; 21 | using Xamarin.Forms; 22 | using Xamarin.Forms.Platform.iOS; 23 | 24 | 25 | [assembly: ExportRenderer(typeof(PullToRefreshLayout), typeof(PullToRefreshLayoutRenderer))] 26 | namespace Refractored.XamForms.PullToRefresh.iOS 27 | { 28 | 29 | /// 30 | /// Pull to refresh layout renderer. 31 | /// 32 | [Preserve(AllMembers = true)] 33 | public class PullToRefreshLayoutRenderer : ViewRenderer 34 | { 35 | 36 | /// 37 | /// Used for registration with dependency service 38 | /// 39 | public async static void Init() 40 | { 41 | var temp = DateTime.Now; 42 | } 43 | 44 | UIRefreshControl refreshControl; 45 | UIView refreshControlParent; 46 | 47 | 48 | /// 49 | /// Raises the element changed event. 50 | /// 51 | /// E. 52 | protected override void OnElementChanged(ElementChangedEventArgs e) 53 | { 54 | base.OnElementChanged(e); 55 | 56 | if (e.OldElement != null || Element == null) 57 | return; 58 | 59 | refreshControl = new UIRefreshControl(); 60 | 61 | refreshControl.ValueChanged += OnRefresh; 62 | 63 | this.refreshControlParent = this; 64 | 65 | UpdateColors(); 66 | UpdateIsRefreshing(); 67 | UpdateIsSwipeToRefreshEnabled(); 68 | } 69 | 70 | bool set; 71 | nfloat origininalY; 72 | 73 | bool TryOffsetRefresh(UIView view, bool refreshing, int index = 0) 74 | { 75 | if (view is UITableView) 76 | { 77 | var uiTableView = view as UITableView; 78 | if (!set) 79 | { 80 | origininalY = uiTableView.ContentOffset.Y; 81 | set = true; 82 | } 83 | 84 | if (origininalY < 0) 85 | return true; 86 | 87 | if (refreshing) 88 | uiTableView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true); 89 | else 90 | uiTableView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true); 91 | return true; 92 | } 93 | 94 | if (view is UICollectionView) 95 | { 96 | 97 | var uiCollectionView = view as UICollectionView; 98 | if (!set) 99 | { 100 | origininalY = uiCollectionView.ContentOffset.Y; 101 | set = true; 102 | } 103 | 104 | if (origininalY < 0) 105 | return true; 106 | 107 | if (refreshing) 108 | uiCollectionView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true); 109 | else 110 | uiCollectionView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true); 111 | return true; 112 | } 113 | 114 | 115 | if (view is UIWebView) 116 | { 117 | //can't do anything 118 | return true; 119 | } 120 | 121 | 122 | if (view is UIScrollView) 123 | { 124 | var uiScrollView = view as UIScrollView; 125 | 126 | if (!set) 127 | { 128 | origininalY = uiScrollView.ContentOffset.Y; 129 | set = true; 130 | } 131 | 132 | if (origininalY < 0) 133 | return true; 134 | 135 | if (refreshing) 136 | uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true); 137 | else 138 | uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true); 139 | return true; 140 | } 141 | 142 | if (view.Subviews == null) 143 | return false; 144 | 145 | for (int i = 0; i < view.Subviews.Length; i++) 146 | { 147 | var control = view.Subviews[i]; 148 | if (TryOffsetRefresh(control, refreshing, i)) 149 | return true; 150 | } 151 | 152 | return false; 153 | } 154 | 155 | 156 | bool TryInsertRefresh(UIView view, int index = 0) 157 | { 158 | this.refreshControlParent = view; 159 | 160 | if (view is UITableView || view is UICollectionView) 161 | { 162 | view.InsertSubview(refreshControl, index); 163 | return true; 164 | } 165 | 166 | if (view is UIWebView) 167 | { 168 | var uiWebView = view as UIWebView; 169 | uiWebView.ScrollView.InsertSubview(refreshControl, index); 170 | return true; 171 | } 172 | 173 | if (view is UIScrollView) 174 | { 175 | var uiScrollView = view as UIScrollView; 176 | view.InsertSubview(refreshControl, index); 177 | uiScrollView.AlwaysBounceVertical = true; 178 | 179 | //override default refreshcolor if in xaml or behind 180 | if (uiScrollView.ContentOffset.Y == 0) 181 | { 182 | uiScrollView.ContentOffset = (new CoreGraphics.CGPoint(0, -refreshControl.Frame.Size.Height * 2.0)); 183 | } 184 | 185 | return true; 186 | } 187 | 188 | if (view.Subviews == null) 189 | return false; 190 | 191 | for (int i = 0; i < view.Subviews.Length; i++) 192 | { 193 | var control = view.Subviews[i]; 194 | if (TryInsertRefresh(control, i)) 195 | return true; 196 | } 197 | 198 | return false; 199 | } 200 | 201 | 202 | 203 | BindableProperty rendererProperty; 204 | 205 | /// 206 | /// Gets the bindable property. 207 | /// 208 | /// The bindable property. 209 | BindableProperty RendererProperty 210 | { 211 | get 212 | { 213 | if (rendererProperty != null) 214 | return rendererProperty; 215 | 216 | var type = Type.GetType("Xamarin.Forms.Platform.iOS.Platform, Xamarin.Forms.Platform.iOS"); 217 | var prop = type.GetField("RendererProperty"); 218 | var val = prop.GetValue(null); 219 | rendererProperty = val as BindableProperty; 220 | 221 | return rendererProperty; 222 | } 223 | } 224 | 225 | void UpdateColors() 226 | { 227 | if (RefreshView == null) 228 | return; 229 | if (RefreshView.RefreshColor != Color.Default) 230 | refreshControl.TintColor = RefreshView.RefreshColor.ToUIColor(); 231 | if (RefreshView.RefreshBackgroundColor != Color.Default) 232 | refreshControl.BackgroundColor = RefreshView.RefreshBackgroundColor.ToUIColor(); 233 | } 234 | 235 | 236 | void UpdateIsRefreshing() 237 | { 238 | IsRefreshing = RefreshView.IsRefreshing; 239 | } 240 | 241 | void UpdateIsSwipeToRefreshEnabled() 242 | { 243 | if (RefreshView.IsPullToRefreshEnabled) 244 | { 245 | this.TryInsertRefresh(this.refreshControlParent); 246 | } 247 | else 248 | { 249 | if (this.refreshControl.Superview != null) 250 | { 251 | this.refreshControl.RemoveFromSuperview(); 252 | } 253 | } 254 | } 255 | 256 | /// 257 | /// Helpers to cast our element easily 258 | /// Will throw an exception if the Element is not correct 259 | /// 260 | /// The refresh view. 261 | public Refractored.XamForms.PullToRefresh.PullToRefreshLayout RefreshView 262 | { 263 | get { return Element; } 264 | } 265 | 266 | 267 | 268 | bool isRefreshing; 269 | 270 | /// 271 | /// Gets or sets a value indicating whether this instance is refreshing. 272 | /// 273 | /// true if this instance is refreshing; otherwise, false. 274 | public bool IsRefreshing 275 | { 276 | get { return isRefreshing; } 277 | set 278 | { 279 | bool changed = IsRefreshing != value; 280 | 281 | isRefreshing = value; 282 | if (isRefreshing) 283 | refreshControl.BeginRefreshing(); 284 | else 285 | refreshControl.EndRefreshing(); 286 | 287 | if (changed) 288 | TryOffsetRefresh(this, IsRefreshing); 289 | } 290 | } 291 | 292 | /// 293 | /// The refresh view has been refreshed 294 | /// 295 | void OnRefresh(object sender, EventArgs e) 296 | { 297 | if (RefreshView?.RefreshCommand?.CanExecute(RefreshView?.RefreshCommandParameter) ?? false) 298 | { 299 | RefreshView.RefreshCommand.Execute(RefreshView?.RefreshCommandParameter); 300 | } 301 | } 302 | 303 | /// 304 | /// Raises the element property changed event. 305 | /// 306 | /// Sender. 307 | /// E. 308 | protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) 309 | { 310 | base.OnElementPropertyChanged(sender, e); 311 | if (e.PropertyName == PullToRefreshLayout.IsPullToRefreshEnabledProperty.PropertyName) 312 | UpdateIsSwipeToRefreshEnabled(); 313 | else if (e.PropertyName == PullToRefreshLayout.IsRefreshingProperty.PropertyName) 314 | UpdateIsRefreshing(); 315 | else if (e.PropertyName == PullToRefreshLayout.RefreshColorProperty.PropertyName) 316 | UpdateColors(); 317 | else if (e.PropertyName == PullToRefreshLayout.RefreshBackgroundColorProperty.PropertyName) 318 | UpdateColors(); 319 | } 320 | 321 | /// 322 | /// Dispose the specified disposing. 323 | /// 324 | /// If set to true disposing. 325 | protected override void Dispose(bool disposing) 326 | { 327 | base.Dispose(disposing); 328 | if (refreshControl != null) 329 | { 330 | refreshControl.ValueChanged -= OnRefresh; 331 | } 332 | 333 | this.refreshControlParent = null; 334 | } 335 | 336 | } 337 | 338 | 339 | } 340 | 341 | -------------------------------------------------------------------------------- /PullToRefreshLayout.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Refractored.XamForms.PullToRefresh 5 | $version$ 6 | PullToRefresh Layout for Xamarin.Forms 7 | James Montemagno 8 | James Montemagno 9 | https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout 10 | https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout 11 | http://www.refractored.com/images/pull_to_refresh.png 12 | false 13 | 14 | Add Pull to Refresh to ScrollView or group a ListView. See Project for more details 15 | Ensure you call PullToRefreshLayoutRenderer.Init() on each platform! 16 | Built against: 3.6.0.220655 17 | 18 | Enhanced Pull To Refresh Layout for Xamarin.Forms 19 | xamarin, pcl, xam.pcl, android, xamarin.forms, ios, pull to refresh, refresh, swipe 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /PullToRefreshLayout.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2027 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshSample", "RefreshSample\RefreshSample.csproj", "{E8A3B142-4FDA-44D2-AC4C-2578150D4E27}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RefreshSample.iOS", "iOS\RefreshSample.iOS.csproj", "{D2E6E0DD-948A-425C-9947-970223689429}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RefreshSample.Droid", "Droid\RefreshSample.Droid.csproj", "{FED4CE90-0F61-492D-94A1-1B3229A6A75B}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{A93D74B9-4C48-418F-BD96-9456DE5408BB}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6E0D1FE0-81AF-48A0-82D9-C9ADD3336E9D}" 15 | ProjectSection(SolutionItems) = preProject 16 | CHANGELOG.md = CHANGELOG.md 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PullToRefresh", "PullToRefresh\PullToRefresh.Standard\PullToRefresh.csproj", "{10AB134C-D1B9-4589-8121-3CEB3D21D096}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|iPhone = Debug|iPhone 26 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 27 | Release|Any CPU = Release|Any CPU 28 | Release|iPhone = Release|iPhone 29 | Release|iPhoneSimulator = Release|iPhoneSimulator 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Debug|iPhone.ActiveCfg = Debug|Any CPU 35 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Debug|iPhone.Build.0 = Debug|Any CPU 36 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 37 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 38 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Release|iPhone.ActiveCfg = Release|Any CPU 40 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Release|iPhone.Build.0 = Release|Any CPU 41 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 42 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 43 | {D2E6E0DD-948A-425C-9947-970223689429}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 44 | {D2E6E0DD-948A-425C-9947-970223689429}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 45 | {D2E6E0DD-948A-425C-9947-970223689429}.Debug|iPhone.ActiveCfg = Debug|iPhone 46 | {D2E6E0DD-948A-425C-9947-970223689429}.Debug|iPhone.Build.0 = Debug|iPhone 47 | {D2E6E0DD-948A-425C-9947-970223689429}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 48 | {D2E6E0DD-948A-425C-9947-970223689429}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 49 | {D2E6E0DD-948A-425C-9947-970223689429}.Release|Any CPU.ActiveCfg = Release|iPhone 50 | {D2E6E0DD-948A-425C-9947-970223689429}.Release|iPhone.ActiveCfg = Release|iPhone 51 | {D2E6E0DD-948A-425C-9947-970223689429}.Release|iPhone.Build.0 = Release|iPhone 52 | {D2E6E0DD-948A-425C-9947-970223689429}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 53 | {D2E6E0DD-948A-425C-9947-970223689429}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 54 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 57 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|iPhone.ActiveCfg = Debug|Any CPU 58 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|iPhone.Build.0 = Debug|Any CPU 59 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 60 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 61 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Release|iPhone.ActiveCfg = Release|Any CPU 63 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Release|iPhone.Build.0 = Release|Any CPU 64 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 65 | {FED4CE90-0F61-492D-94A1-1B3229A6A75B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 66 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Debug|iPhone.ActiveCfg = Debug|Any CPU 69 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Debug|iPhone.Build.0 = Debug|Any CPU 70 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 71 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 72 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Release|iPhone.ActiveCfg = Release|Any CPU 75 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Release|iPhone.Build.0 = Release|Any CPU 76 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 77 | {10AB134C-D1B9-4589-8121-3CEB3D21D096}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 78 | EndGlobalSection 79 | GlobalSection(SolutionProperties) = preSolution 80 | HideSolutionNode = FALSE 81 | EndGlobalSection 82 | GlobalSection(NestedProjects) = preSolution 83 | {10AB134C-D1B9-4589-8121-3CEB3D21D096} = {A93D74B9-4C48-418F-BD96-9456DE5408BB} 84 | EndGlobalSection 85 | GlobalSection(ExtensibilityGlobals) = postSolution 86 | SolutionGuid = {8BF71540-5C4B-4293-AEAB-506D03CE04C7} 87 | EndGlobalSection 88 | EndGlobal 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PullToRefreshLayout for Xamarin.Forms 2 | =================================== 3 | 4 | ## Update - Officially Deprecated 5 | 6 | This library is officially deprecated and you can now use the official [RefreshView](https://github.com/xamarin/Xamarin.Forms/issues/5882) as part of [Xamarin.Forms 4.3](https://docs.microsoft.com/xamarin/xamarin-forms/release-notes/4.3/4.3.0-pre3?WT.mc_id=xamarin.formspulltorefreshlayout-github-jamont). No further work will be done on this library and all issues will be closed. 7 | 8 | 9 | -------------------------------- 10 | 11 | 12 | Implementation of pull to refresh layout for Xamarin.Forms targeting iOS and Android. 13 | 14 | The goal was to create a cross-platform Xamarin.Forms Layout that could have it’s content set to anything and would enable pull to refresh capabilities. 15 | 16 | A BIG thank you to [Jason Smith](https://twitter.com/jassmith87) for his assistance on some tricky logic. 17 | 18 | For a detailed overview of the API and sample please read through my blog: 19 | http://motzcod.es/post/128274430137/pull-to-refresh-anyish-xamarinforms-view 20 | 21 | Also available as a NuGet: https://www.nuget.org/packages/Refractored.XamForms.PullToRefresh/ 22 | 23 | Build Status: [![Build status](https://dev.azure.com/jamesmontemagno/Plugins%20for%20Xamarin/_apis/build/status/PullToRefresh)](https://dev.azure.com/jamesmontemagno/Plugins%20for%20Xamarin/_build/latest?definitionId=0) 24 | 25 | **Important** 26 | 27 | If you are using the NuGet ensure that you call “PullToRefreshLayoutRenderer.Init();” in both your MainActivity and AppDelegate. (Similar to Xamarin.Forms.Maps). 28 | 29 | 30 | Android: You must build and compile against SDK 8.1 or 9.0. 31 | 32 | 33 | ![](demo.gif) 34 | 35 | ### API 36 | Simply create a PullToRefreshLayout like any other Layout and set the Content to a supported view. 37 | 38 | 39 | ```csharp 40 | var scrollView = new ScrollView 41 | { 42 | VerticalOptions = LayoutOptions.FillAndExpand, 43 | HorizontalOptions = LayoutOptions.FillAndExpand, 44 | Content = /* Anything you want in your ScrollView */ 45 | }; 46 | 47 | var refreshView = new PullToRefreshLayout { 48 | VerticalOptions = LayoutOptions.FillAndExpand, 49 | HorizontalOptions = LayoutOptions.FillAndExpand, 50 | Content = scrollView, 51 | RefreshColor = Color.FromHex("#3498db") 52 | }; 53 | 54 | //Set Bindings 55 | refreshView.SetBinding (PullToRefreshLayout.IsRefreshingProperty, vm => vm.IsBusy, BindingMode.OneWay); 56 | refreshView.SetBinding(PullToRefreshLayout.RefreshCommandProperty, vm => vm.RefreshCommand); 57 | 58 | Content = refreshView; 59 | ``` 60 | 61 | Additionally, your content could be anything you want including a StackLayout: 62 | 63 | ```csharp 64 | Content = new StackLayout 65 | { 66 | Children = 67 | { 68 | new Label { Text = “Hello Pull to Refresh“ }, 69 | refreshView 70 | } 71 | }; 72 | ``` 73 | 74 | You can of course do it in XAML: 75 | ``` 76 | 77 | 83 | 88 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | ### Officially Supported Views: 112 | * ListView 113 | * ScrollView 114 | * CollectionView 115 | 116 | ### Unofficial Supported Views 117 | * UICollectionView (iOS) 118 | * RecyclerView (Android) 119 | * GridView (Android) 120 | * Potentially all Android Views without a scroll, but it acts a bit odd on some 121 | * ^^You should probably use a ScrollView^^ 122 | 123 | ### License 124 | The MIT License (MIT) 125 | 126 | Copyright (c) 2015 Refractored LLC & James Montemagno 127 | 128 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 129 | 130 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 131 | 132 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /RefreshSample.userprefs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RefreshSample/App.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using Xamarin.Forms; 16 | using RefreshSample.Views; 17 | 18 | namespace RefreshSample 19 | { 20 | public class App : Application 21 | { 22 | Page scrollViewPage; 23 | public App() 24 | { 25 | 26 | var scrollView = new Button { Text = "ScrollView" }; 27 | var scrollViewXaml = new Button { Text = "ScrollView Xaml" }; 28 | var listView = new Button { Text = "ListView" }; 29 | var stackLayout = new Button { Text = "StackLayout" }; 30 | var grid = new Button { Text = "Grid" }; 31 | var scrollViewIn = new Button { Text = "ScrollView" }; 32 | var scrollViewManual = new Button { Text = "ScrollView Manual" }; 33 | var listViewIn = new Button { Text = "ListView" }; 34 | var stackLayoutIn = new Button { Text = "StackLayout" }; 35 | var gridIn = new Button { Text = "Grid" }; 36 | var disposeCrash = new Button { Text = "Dispose Crash" }; 37 | var disposeCrash2 = new Button { Text = "Dispose Crash" }; 38 | 39 | 40 | scrollViewPage = new ScrollViewPage(false); 41 | Page page; 42 | // The root page of your application 43 | MainPage = page = new NavigationPage(new ContentPage 44 | { 45 | Title = "Pull to Refresh!", 46 | Content = new ScrollView 47 | { 48 | Content = new StackLayout 49 | { 50 | Padding = 25, 51 | Children = 52 | { 53 | new Label 54 | { 55 | Text = "Pull to Refresh in:" 56 | }, 57 | scrollView, 58 | listView, 59 | stackLayout, 60 | grid, 61 | scrollViewXaml, 62 | new Label 63 | { 64 | Text = "Inside a layout:" 65 | }, 66 | scrollViewIn, 67 | scrollViewManual, 68 | listViewIn, 69 | stackLayoutIn, 70 | gridIn, 71 | new Label 72 | { 73 | Text = "Misc" 74 | }, 75 | disposeCrash, 76 | disposeCrash2 77 | } 78 | } 79 | } 80 | }) 81 | { 82 | BackgroundColor = Color.FromHex("3498db"), 83 | BarTextColor = Color.White 84 | }; 85 | 86 | scrollView.Clicked += async (sender, e) => 87 | { 88 | var page2 = new ScrollViewXamlPage(); 89 | await page.Navigation.PushAsync(scrollViewPage); 90 | await scrollViewPage.Navigation.PushAsync(page2); 91 | }; 92 | scrollViewXaml.Clicked += (sender, e) => page.Navigation.PushAsync(new ScrollViewXamlPage()); 93 | scrollViewIn.Clicked += (sender, e) => page.Navigation.PushAsync(new ScrollViewPage(true)); 94 | listView.Clicked += (sender, e) => page.Navigation.PushAsync(new ListViewPage(false)); 95 | listViewIn.Clicked += (sender, e) => page.Navigation.PushAsync(new ListViewPage(true)); 96 | stackLayout.Clicked += (sender, e) => page.Navigation.PushAsync(new StackLayoutPage(false)); 97 | stackLayoutIn.Clicked += (sender, e) => page.Navigation.PushAsync(new StackLayoutPage(true)); 98 | grid.Clicked += (sender, e) => page.Navigation.PushAsync(new GridPage(false)); 99 | gridIn.Clicked += (sender, e) => page.Navigation.PushAsync(new GridPage(true)); 100 | disposeCrash.Clicked += (sender, e) => MainPage = new TabbedPage1(); 101 | disposeCrash2.Clicked += (sender, e) => MainPage = new DisposeCrashPage(); 102 | scrollViewManual.Clicked += (sender, e) => page.Navigation.PushAsync(new ScrollViewPageManual(true)); 103 | } 104 | 105 | protected override void OnStart() 106 | { 107 | // Handle when your app starts 108 | } 109 | 110 | protected override void OnSleep() 111 | { 112 | // Handle when your app sleeps 113 | } 114 | 115 | protected override void OnResume() 116 | { 117 | // Handle when your app resumes 118 | } 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /RefreshSample/RefreshSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | RefreshSample 6 | RefreshSample 7 | 1.0.0.0 8 | 1.0.0.0 9 | 1.0.0.0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RefreshSample/ViewModels/TestViewModel.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using System; 17 | using System.ComponentModel; 18 | using System.Collections.ObjectModel; 19 | using Xamarin.Forms; 20 | using System.Threading.Tasks; 21 | using System.Windows.Input; 22 | 23 | namespace RefreshSample.ViewModels 24 | { 25 | public class TestViewModel : INotifyPropertyChanged 26 | { 27 | public ObservableCollection Items { get; set; } 28 | Page page; 29 | public TestViewModel(Page page) 30 | { 31 | this.page = page; 32 | Items = new ObservableCollection(); 33 | } 34 | 35 | bool canRefresh = true; 36 | 37 | public bool CanRefresh 38 | { 39 | get { return canRefresh; } 40 | set 41 | { 42 | if (canRefresh == value) 43 | return; 44 | 45 | canRefresh = value; 46 | OnPropertyChanged("CanRefresh"); 47 | } 48 | } 49 | 50 | 51 | bool isBusy; 52 | 53 | public bool IsBusy 54 | { 55 | get { return isBusy; } 56 | set 57 | { 58 | if (isBusy == value) 59 | return; 60 | 61 | isBusy = value; 62 | OnPropertyChanged("IsBusy"); 63 | } 64 | } 65 | 66 | ICommand refreshCommand; 67 | 68 | public ICommand RefreshCommand 69 | { 70 | get { return refreshCommand ?? (refreshCommand = new Command(async () => await ExecuteRefreshCommand())); } 71 | } 72 | 73 | async Task ExecuteRefreshCommand() 74 | { 75 | if (IsBusy) 76 | return; 77 | 78 | IsBusy = true; 79 | Items.Clear(); 80 | 81 | Device.StartTimer(TimeSpan.FromSeconds(5), () => 82 | { 83 | 84 | for (int i = 0; i < 100; i++) 85 | Items.Add(DateTime.Now.AddMinutes(i).ToString("F")); 86 | 87 | IsBusy = false; 88 | 89 | page.DisplayAlert("Refreshed", "You just refreshed the page! Nice job! Pull to refresh is now disabled", "OK"); 90 | this.CanRefresh = false; 91 | 92 | return false; 93 | }); 94 | } 95 | 96 | #region INotifyPropertyChanged implementation 97 | 98 | public event PropertyChangedEventHandler PropertyChanged; 99 | 100 | #endregion 101 | 102 | public void OnPropertyChanged(string propertyName) 103 | { 104 | if (PropertyChanged == null) 105 | return; 106 | 107 | PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 108 | } 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /RefreshSample/Views/DisposeCrashPage.cs: -------------------------------------------------------------------------------- 1 | using Refractored.XamForms.PullToRefresh; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xamarin.Forms; 8 | 9 | namespace RefreshSample.Views 10 | { 11 | class DisposeCrashPage : ContentPage 12 | { 13 | private readonly View mainView; 14 | 15 | public DisposeCrashPage() 16 | { 17 | Title = "Test Page"; 18 | mainView = new PullToRefreshLayout 19 | { 20 | Content = new ScrollView() 21 | { 22 | Content = new Label() { Text = "This is a test." } 23 | } 24 | }; 25 | Content = mainView; 26 | } 27 | 28 | protected override async void OnAppearing() 29 | { 30 | base.OnAppearing(); 31 | 32 | Content = new ActivityIndicator { HorizontalOptions = LayoutOptions.Center, IsEnabled = true, IsVisible = true, IsRunning = true }; 33 | await Task.Delay(3000); 34 | try 35 | { 36 | Content = mainView; 37 | } 38 | catch (ObjectDisposedException ex) 39 | { 40 | // Just a place to put a debug breakpoint 41 | throw; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /RefreshSample/Views/GridPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using Xamarin.Forms; 17 | using RefreshSample.ViewModels; 18 | using Refractored.XamForms.PullToRefresh; 19 | 20 | namespace RefreshSample.Views 21 | { 22 | public class GridPage : ContentPage 23 | { 24 | public GridPage(bool insideLayout) 25 | {var random = new Random(); 26 | Title = "StackLayout (Pull to Refresh)"; 27 | 28 | BindingContext = new TestViewModel (this); 29 | 30 | var grid = new Grid 31 | { 32 | VerticalOptions = LayoutOptions.FillAndExpand, 33 | HorizontalOptions = LayoutOptions.FillAndExpand, 34 | RowSpacing = 0, 35 | ColumnSpacing = 0 36 | }; 37 | 38 | for (int i = 0; i < 20; i++) 39 | { 40 | grid.ColumnDefinitions.Add(new ColumnDefinition{ Width = GridLength.Auto }); 41 | for (int j = 0; j < 20; j++) 42 | { 43 | 44 | if (i == 0) 45 | { 46 | grid.RowDefinitions.Add(new RowDefinition{ Height = GridLength.Auto }); 47 | 48 | } 49 | 50 | grid.Children.Add(new BoxView 51 | { 52 | HeightRequest = 50, 53 | VerticalOptions = LayoutOptions.FillAndExpand, 54 | HorizontalOptions = LayoutOptions.FillAndExpand, 55 | BackgroundColor = Color.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)) 56 | }, i, j); 57 | } 58 | 59 | } 60 | 61 | var refreshView = new PullToRefreshLayout { 62 | VerticalOptions = LayoutOptions.FillAndExpand, 63 | HorizontalOptions = LayoutOptions.FillAndExpand, 64 | Content = grid, 65 | RefreshColor = Color.FromHex("#3498db") 66 | }; 67 | 68 | refreshView.SetBinding (PullToRefreshLayout.IsRefreshingProperty, vm => vm.IsBusy, BindingMode.OneWay); 69 | refreshView.SetBinding(PullToRefreshLayout.RefreshCommandProperty, vm => vm.RefreshCommand); 70 | 71 | 72 | if (insideLayout) 73 | { 74 | var activity = new ActivityIndicator(); 75 | activity.SetBinding(ActivityIndicator.IsRunningProperty, "IsBusy"); 76 | 77 | Content = new StackLayout 78 | { 79 | Spacing = 0, 80 | Children = 81 | { 82 | activity, 83 | new Label 84 | { 85 | TextColor = Color.White, 86 | Text = "In a StackLayout", 87 | FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)), 88 | BackgroundColor = Color.FromHex("#3498db"), 89 | XAlign = TextAlignment.Center, 90 | HorizontalOptions = LayoutOptions.FillAndExpand 91 | }, 92 | refreshView 93 | } 94 | }; 95 | } 96 | else 97 | { 98 | Content = refreshView; 99 | } 100 | } 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /RefreshSample/Views/ListViewPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using Xamarin.Forms; 17 | using RefreshSample.ViewModels; 18 | using Refractored.XamForms.PullToRefresh; 19 | using System.Threading.Tasks; 20 | 21 | namespace RefreshSample.Views 22 | { 23 | public class ListViewPage : ContentPage 24 | { 25 | public ListViewPage(bool insideLayout) 26 | { 27 | Title = "ListView (Pull to Refresh)"; 28 | 29 | BindingContext = new TestViewModel(this); 30 | 31 | var listView = new ListView(); 32 | //ListView now has a built in pull to refresh! 33 | //You most likely could enable the listview pull to refresh and use it instead of the refresh view 34 | //listView.IsPullToRefreshEnabled = true; 35 | // 36 | //listView.SetBinding(ListView.IsRefreshingProperty, vm => vm.IsBusy, BindingMode.OneWay); 37 | //listView.SetBinding(ListView.RefreshCommandProperty, vm => vm.RefreshCommand); 38 | 39 | 40 | 41 | listView.SetBinding(ItemsView.ItemsSourceProperty, new Binding("Items")); 42 | 43 | //ListView now has a built in pull to refresh! 44 | //You most likely could disable the listview pull to refresh and re-enable this 45 | //However, I wouldn't recommend that. 46 | var refreshView = new PullToRefreshLayout 47 | { 48 | VerticalOptions = LayoutOptions.FillAndExpand, 49 | HorizontalOptions = LayoutOptions.FillAndExpand, 50 | Content = new StackLayout 51 | { 52 | Spacing = 0, 53 | Children = 54 | { 55 | new Label 56 | { 57 | TextColor = Color.White, 58 | Text = "In a StackLayout", 59 | FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)), 60 | BackgroundColor = Color.FromHex("#3498db"), 61 | VerticalTextAlignment = TextAlignment.Center, 62 | HorizontalOptions = LayoutOptions.FillAndExpand 63 | }, 64 | listView 65 | } 66 | }, 67 | RefreshColor = Color.FromHex("#3498db") 68 | }; 69 | 70 | refreshView.SetBinding(PullToRefreshLayout.IsRefreshingProperty, new Binding("IsBusy", BindingMode.OneWay)); 71 | refreshView.SetBinding(PullToRefreshLayout.RefreshCommandProperty, new Binding("RefreshCommand")); 72 | refreshView.SetBinding(PullToRefreshLayout.IsPullToRefreshEnabledProperty, new Binding("CanRefresh")); 73 | 74 | if (insideLayout) 75 | { 76 | Content = new StackLayout 77 | { 78 | Spacing = 0, 79 | Children = 80 | { 81 | new Label 82 | { 83 | TextColor = Color.White, 84 | Text = "In a StackLayout", 85 | FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)), 86 | BackgroundColor = Color.FromHex("#3498db"), 87 | VerticalTextAlignment = TextAlignment.Center, 88 | HorizontalOptions = LayoutOptions.FillAndExpand 89 | }, 90 | refreshView 91 | } 92 | }; 93 | } 94 | else 95 | { 96 | Content = refreshView; 97 | } 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /RefreshSample/Views/ScrollViewPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using Xamarin.Forms; 17 | using RefreshSample.ViewModels; 18 | using Refractored.XamForms.PullToRefresh; 19 | 20 | namespace RefreshSample.Views 21 | { 22 | public class ScrollViewPage : ContentPage 23 | { 24 | public ScrollViewPage(bool insideLayout) 25 | { 26 | var random = new Random(); 27 | Title = "ScrollView (Pull to Refresh)"; 28 | 29 | BindingContext = new TestViewModel(this); 30 | 31 | 32 | var grid = new Grid 33 | { 34 | VerticalOptions = LayoutOptions.FillAndExpand, 35 | HorizontalOptions = LayoutOptions.FillAndExpand, 36 | RowSpacing = 0, 37 | ColumnSpacing = 0 38 | }; 39 | 40 | var scrollView = new ScrollView 41 | { 42 | VerticalOptions = LayoutOptions.FillAndExpand, 43 | HorizontalOptions = LayoutOptions.FillAndExpand, 44 | Content = grid 45 | }; 46 | 47 | 48 | for (int i = 0; i < 20; i++) 49 | { 50 | grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); 51 | for (int j = 0; j < 20; j++) 52 | { 53 | 54 | if (i == 0) 55 | { 56 | grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); 57 | 58 | } 59 | 60 | grid.Children.Add(new BoxView 61 | { 62 | HeightRequest = 50, 63 | VerticalOptions = LayoutOptions.FillAndExpand, 64 | HorizontalOptions = LayoutOptions.FillAndExpand, 65 | BackgroundColor = Color.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)) 66 | }, i, j); 67 | } 68 | 69 | } 70 | 71 | 72 | var refreshView = new PullToRefreshLayout 73 | { 74 | VerticalOptions = LayoutOptions.FillAndExpand, 75 | HorizontalOptions = LayoutOptions.FillAndExpand, 76 | Content = scrollView, 77 | RefreshColor = Color.FromHex("#3498db") 78 | }; 79 | 80 | refreshView.SetBinding(PullToRefreshLayout.IsRefreshingProperty, new Binding("IsBusy", BindingMode.OneWay)); 81 | refreshView.SetBinding(PullToRefreshLayout.RefreshCommandProperty, new Binding("RefreshCommand")); 82 | 83 | 84 | 85 | if (insideLayout) 86 | { 87 | Content = new StackLayout 88 | { 89 | Spacing = 0, 90 | Children = 91 | { 92 | new Label 93 | { 94 | TextColor = Color.White, 95 | Text = "In a StackLayout", 96 | FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)), 97 | BackgroundColor = Color.FromHex("#3498db"), 98 | VerticalTextAlignment = TextAlignment.Center, 99 | HorizontalOptions = LayoutOptions.FillAndExpand 100 | }, 101 | refreshView 102 | } 103 | }; 104 | } 105 | else 106 | { 107 | Content = refreshView; 108 | } 109 | } 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /RefreshSample/Views/ScrollViewPageManual.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using Xamarin.Forms; 17 | using RefreshSample.ViewModels; 18 | using Refractored.XamForms.PullToRefresh; 19 | 20 | namespace RefreshSample.Views 21 | { 22 | public class ScrollViewPageManual : ContentPage 23 | { 24 | public ScrollViewPageManual(bool insideLayout) 25 | { 26 | var random = new Random(); 27 | Title = "ScrollView (Pull to Refresh)"; 28 | 29 | BindingContext = new TestViewModel (this); 30 | var buttonStart = new Button { Text = "Start" }; 31 | var buttonStop = new Button { Text = "Stop" }; 32 | var buttonStartStop = new Button { Text = "Start/Stop" }; 33 | 34 | var grid = new Grid 35 | { 36 | VerticalOptions = LayoutOptions.FillAndExpand, 37 | HorizontalOptions = LayoutOptions.FillAndExpand, 38 | RowSpacing = 0, 39 | ColumnSpacing = 0 40 | }; 41 | 42 | var scrollView = new ScrollView 43 | { 44 | VerticalOptions = LayoutOptions.FillAndExpand, 45 | HorizontalOptions = LayoutOptions.FillAndExpand, 46 | Content = grid 47 | }; 48 | 49 | 50 | for (int i = 0; i < 20; i++) 51 | { 52 | grid.ColumnDefinitions.Add(new ColumnDefinition{ Width = GridLength.Auto }); 53 | for (int j = 0; j < 20; j++) 54 | { 55 | 56 | if (i == 0) 57 | { 58 | grid.RowDefinitions.Add(new RowDefinition{ Height = GridLength.Auto }); 59 | 60 | } 61 | 62 | grid.Children.Add(new BoxView 63 | { 64 | HeightRequest = 50, 65 | VerticalOptions = LayoutOptions.FillAndExpand, 66 | HorizontalOptions = LayoutOptions.FillAndExpand, 67 | BackgroundColor = Color.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)) 68 | }, i, j); 69 | } 70 | 71 | } 72 | 73 | 74 | var refreshView = new PullToRefreshLayout { 75 | VerticalOptions = LayoutOptions.FillAndExpand, 76 | HorizontalOptions = LayoutOptions.FillAndExpand, 77 | Content = scrollView, 78 | RefreshColor = Color.FromHex("#3498db") 79 | }; 80 | 81 | 82 | buttonStart.Clicked += (sender, args) => refreshView.IsRefreshing = true; 83 | buttonStop.Clicked += (sender, args) => refreshView.IsRefreshing = false; 84 | buttonStartStop.Clicked += (sender, args) => 85 | { 86 | refreshView.IsRefreshing = true; 87 | refreshView.IsRefreshing = false; 88 | }; 89 | 90 | 91 | if (insideLayout) 92 | { 93 | Content = new StackLayout 94 | { 95 | Spacing = 0, 96 | Children = 97 | { 98 | new Label 99 | { 100 | TextColor = Color.White, 101 | Text = "In a StackLayout", 102 | FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)), 103 | BackgroundColor = Color.FromHex("#3498db"), 104 | HorizontalTextAlignment = TextAlignment.Center, 105 | HorizontalOptions = LayoutOptions.FillAndExpand 106 | }, 107 | buttonStart, 108 | buttonStop, 109 | buttonStartStop, 110 | refreshView 111 | } 112 | }; 113 | } 114 | else 115 | { 116 | Content = refreshView; 117 | } 118 | } 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /RefreshSample/Views/ScrollViewXamlPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 8 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /RefreshSample/Views/ScrollViewXamlPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using Xamarin.Forms; 5 | using RefreshSample.ViewModels; 6 | 7 | namespace RefreshSample.Views 8 | { 9 | public partial class ScrollViewXamlPage : ContentPage 10 | { 11 | public ScrollViewXamlPage() 12 | { 13 | InitializeComponent(); 14 | BindingContext = new TestViewModel(this); 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /RefreshSample/Views/StackLayoutPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Refractored LLC & James Montemagno: 3 | * http://github.com/JamesMontemagno 4 | * http://twitter.com/JamesMontemagno 5 | * http://refractored.com 6 | * 7 | * The MIT License (MIT) see GitHub For more information 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | using System; 16 | using Xamarin.Forms; 17 | using RefreshSample.ViewModels; 18 | using Refractored.XamForms.PullToRefresh; 19 | 20 | namespace RefreshSample.Views 21 | { 22 | public class StackLayoutPage: ContentPage 23 | { 24 | public StackLayoutPage(bool insideLayout) 25 | { 26 | var random = new Random(); 27 | Title = "StackLayout (Pull to Refresh)"; 28 | 29 | BindingContext = new TestViewModel (this); 30 | 31 | var stack = new StackLayout 32 | { 33 | VerticalOptions = LayoutOptions.FillAndExpand, 34 | HorizontalOptions = LayoutOptions.FillAndExpand, 35 | Spacing = 0 36 | }; 37 | 38 | for (int i = 0; i < 20; i++) 39 | { 40 | stack.Children.Add(new BoxView 41 | { 42 | HeightRequest = 150, 43 | HorizontalOptions = LayoutOptions.FillAndExpand, 44 | BackgroundColor = Color.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)) 45 | }); 46 | } 47 | 48 | var refreshView = new PullToRefreshLayout { 49 | VerticalOptions = LayoutOptions.FillAndExpand, 50 | HorizontalOptions = LayoutOptions.FillAndExpand, 51 | Content = stack, 52 | RefreshColor = Color.FromHex("#3498db") 53 | }; 54 | 55 | refreshView.SetBinding (PullToRefreshLayout.IsRefreshingProperty, vm => vm.IsBusy, BindingMode.OneWay); 56 | refreshView.SetBinding(PullToRefreshLayout.RefreshCommandProperty, vm => vm.RefreshCommand); 57 | 58 | 59 | if (insideLayout) 60 | { 61 | var activity = new ActivityIndicator(); 62 | activity.SetBinding(ActivityIndicator.IsRunningProperty, "IsBusy"); 63 | Content = new StackLayout 64 | { 65 | Spacing = 0, 66 | Children = 67 | { 68 | activity, 69 | new Label 70 | { 71 | TextColor = Color.White, 72 | Text = "In a StackLayout", 73 | FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)), 74 | BackgroundColor = Color.FromHex("#3498db"), 75 | XAlign = TextAlignment.Center, 76 | HorizontalOptions = LayoutOptions.FillAndExpand 77 | }, 78 | refreshView 79 | } 80 | }; 81 | } 82 | else 83 | { 84 | Content = refreshView; 85 | } 86 | } 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /RefreshSample/Views/TabbedPage1.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /RefreshSample/Views/TabbedPage1.xaml.cs: -------------------------------------------------------------------------------- 1 | using RefreshSample.ViewModels; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | using Xamarin.Forms; 9 | using Xamarin.Forms.Xaml; 10 | 11 | namespace RefreshSample.Views 12 | { 13 | [XamlCompilation(XamlCompilationOptions.Compile)] 14 | public partial class TabbedPage1 : TabbedPage 15 | { 16 | public TabbedPage1() 17 | { 18 | InitializeComponent(); 19 | BindingContext = new TestViewModel(this); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.3.0.{build}-beta 2 | image: Visual Studio 2017 3 | configuration: Release 4 | assembly_info: 5 | patch: true 6 | file: '**\AssemblyInfo.*' 7 | assembly_version: '{version}' 8 | assembly_file_version: '{version}' 9 | assembly_informational_version: '{version}' 10 | install: 11 | - echo y | "C:\Program Files (x86)\Android\android-sdk\tools\android.bat" update sdk -u -a -t tools 12 | - echo y | "C:\Program Files (x86)\Android\android-sdk\tools\android.bat" update sdk -u -a -t platform-tools 13 | - echo y | "C:\Program Files (x86)\Android\android-sdk\tools\android.bat" update sdk -u -a -t build-tools-25.0.6 14 | - echo y | "C:\Program Files (x86)\Android\android-sdk\tools\android.bat" update sdk -u -a -t android-28 15 | - echo y | "C:\Program Files (x86)\Android\android-sdk\tools\android.bat" update sdk -u -a -t android-15 16 | build_script: 17 | - cmd: >- 18 | powershell .\build.ps1 -Target Default -Verbosity diagnostic 19 | artifacts: 20 | - path: ./Build/nuget/*.nupkg 21 | name: NuGet 22 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | var TARGET = Argument ("target", Argument ("t", "Default")); 2 | var VERSION = EnvironmentVariable ("APPVEYOR_BUILD_VERSION") ?? Argument("version", "0.0.9999"); 3 | var CONFIG = Argument("configuration", EnvironmentVariable ("CONFIGURATION") ?? "Release"); 4 | var SLN = "./PullToRefreshLayout.sln"; 5 | 6 | Task("Libraries").Does(()=> 7 | { 8 | NuGetRestore (SLN); 9 | MSBuild (SLN, c => { 10 | c.Configuration = CONFIG; 11 | c.MSBuildPlatform = Cake.Common.Tools.MSBuild.MSBuildPlatform.x86; 12 | }); 13 | }); 14 | 15 | Task ("NuGet") 16 | .IsDependentOn ("Libraries") 17 | .Does (() => 18 | { 19 | if(!DirectoryExists("./Build/nuget/")) 20 | CreateDirectory("./Build/nuget"); 21 | 22 | NuGetPack ("./PullToRefreshLayout.nuspec", new NuGetPackSettings { 23 | Version = VERSION, 24 | OutputDirectory = "./Build/nuget/", 25 | BasePath = "./" 26 | }); 27 | }); 28 | 29 | //Build the component, which build samples, nugets, and libraries 30 | Task ("Default").IsDependentOn("NuGet"); 31 | 32 | Task ("Clean").Does (() => 33 | { 34 | CleanDirectory ("./component/tools/"); 35 | CleanDirectories ("./Build/"); 36 | CleanDirectories ("./**/bin"); 37 | CleanDirectories ("./**/obj"); 38 | }); 39 | 40 | RunTarget (TARGET); 41 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This is the Cake bootstrapper script for PowerShell. 3 | # This file was downloaded from https://github.com/cake-build/resources 4 | # Feel free to change this file to fit your needs. 5 | ########################################################################## 6 | 7 | <# 8 | 9 | .SYNOPSIS 10 | This is a Powershell script to bootstrap a Cake build. 11 | 12 | .DESCRIPTION 13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 14 | and execute your Cake build script with the parameters you provide. 15 | 16 | .PARAMETER Script 17 | The build script to execute. 18 | .PARAMETER Target 19 | The build script target to run. 20 | .PARAMETER Configuration 21 | The build configuration to use. 22 | .PARAMETER Verbosity 23 | Specifies the amount of information to be displayed. 24 | .PARAMETER Experimental 25 | Tells Cake to use the latest Roslyn release. 26 | .PARAMETER WhatIf 27 | Performs a dry run of the build script. 28 | No tasks will be executed. 29 | .PARAMETER Mono 30 | Tells Cake to use the Mono scripting engine. 31 | .PARAMETER SkipToolPackageRestore 32 | Skips restoring of packages. 33 | .PARAMETER ScriptArgs 34 | Remaining arguments are added here. 35 | 36 | .LINK 37 | http://cakebuild.net 38 | 39 | #> 40 | 41 | [CmdletBinding()] 42 | Param( 43 | [string]$Script = "build.cake", 44 | [string]$Target = "Default", 45 | [ValidateSet("Release", "Debug")] 46 | [string]$Configuration = "Release", 47 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 48 | [string]$Verbosity = "Verbose", 49 | [switch]$Experimental, 50 | [Alias("DryRun","Noop")] 51 | [switch]$WhatIf, 52 | [switch]$Mono, 53 | [switch]$SkipToolPackageRestore, 54 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 55 | [string[]]$ScriptArgs 56 | ) 57 | 58 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null 59 | function MD5HashFile([string] $filePath) 60 | { 61 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) 62 | { 63 | return $null 64 | } 65 | 66 | [System.IO.Stream] $file = $null; 67 | [System.Security.Cryptography.MD5] $md5 = $null; 68 | try 69 | { 70 | $md5 = [System.Security.Cryptography.MD5]::Create() 71 | $file = [System.IO.File]::OpenRead($filePath) 72 | return [System.BitConverter]::ToString($md5.ComputeHash($file)) 73 | } 74 | finally 75 | { 76 | if ($file -ne $null) 77 | { 78 | $file.Dispose() 79 | } 80 | } 81 | } 82 | 83 | Write-Host "Preparing to run build script..." 84 | 85 | if(!$PSScriptRoot){ 86 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 87 | } 88 | 89 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 90 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 91 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 92 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 93 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 94 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" 95 | 96 | # Should we use mono? 97 | $UseMono = ""; 98 | if($Mono.IsPresent) { 99 | Write-Verbose -Message "Using the Mono based scripting engine." 100 | $UseMono = "-mono" 101 | } 102 | 103 | # Should we use the new Roslyn? 104 | $UseExperimental = ""; 105 | if($Experimental.IsPresent -and !($Mono.IsPresent)) { 106 | Write-Verbose -Message "Using experimental version of Roslyn." 107 | $UseExperimental = "-experimental" 108 | } 109 | 110 | # Is this a dry run? 111 | $UseDryRun = ""; 112 | if($WhatIf.IsPresent) { 113 | $UseDryRun = "-dryrun" 114 | } 115 | 116 | # Make sure tools folder exists 117 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 118 | Write-Verbose -Message "Creating tools directory..." 119 | New-Item -Path $TOOLS_DIR -Type directory | out-null 120 | } 121 | 122 | # Make sure that packages.config exist. 123 | if (!(Test-Path $PACKAGES_CONFIG)) { 124 | Write-Verbose -Message "Downloading packages.config..." 125 | try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { 126 | Throw "Could not download packages.config." 127 | } 128 | } 129 | 130 | # Try find NuGet.exe in path if not exists 131 | if (!(Test-Path $NUGET_EXE)) { 132 | Write-Verbose -Message "Trying to find nuget.exe in PATH..." 133 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } 134 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 135 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { 136 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." 137 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName 138 | } 139 | } 140 | 141 | # Try download NuGet.exe if not exists 142 | if (!(Test-Path $NUGET_EXE)) { 143 | Write-Verbose -Message "Downloading NuGet.exe..." 144 | try { 145 | (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) 146 | } catch { 147 | Throw "Could not download NuGet.exe." 148 | } 149 | } 150 | 151 | $newVersion = $env:APPVEYOR_BUILD_VERSION.Replace("-beta","") 152 | $configFiles = Get-ChildItem . *.csproj -rec 153 | $assemblyVersionString = "" + $newVersion + "" 154 | $assemblyFileVersionString = "" + $newVersion + "" 155 | $versionString = "" + $newVersion + "" 156 | foreach ($file in $configFiles) 157 | { 158 | (Get-Content $file.PSPath) | 159 | Foreach-Object { $_ -replace "1.0.0.0", $assemblyVersionString } | 160 | Set-Content $file.PSPath 161 | } 162 | 163 | foreach ($file in $configFiles) 164 | { 165 | (Get-Content $file.PSPath) | 166 | Foreach-Object { $_ -replace "1.0.0.0", $assemblyFileVersionString } | 167 | Set-Content $file.PSPath 168 | } 169 | 170 | foreach ($file in $configFiles) 171 | { 172 | (Get-Content $file.PSPath) | 173 | Foreach-Object { $_ -replace "1.0.0.0", $versionString } | 174 | Set-Content $file.PSPath 175 | } 176 | 177 | # Save nuget.exe path to environment to be available to child processed 178 | $ENV:NUGET_EXE = $NUGET_EXE 179 | 180 | # Restore tools from NuGet? 181 | if(-Not $SkipToolPackageRestore.IsPresent) { 182 | Push-Location 183 | Set-Location $TOOLS_DIR 184 | 185 | # Check for changes in packages.config and remove installed tools if true. 186 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 187 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 188 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 189 | Write-Verbose -Message "Missing or changed package.config hash..." 190 | Remove-Item * -Recurse -Exclude packages.config,nuget.exe 191 | } 192 | 193 | Write-Verbose -Message "Restoring tools from NuGet..." 194 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 195 | 196 | if ($LASTEXITCODE -ne 0) { 197 | Throw "An error occured while restoring NuGet tools." 198 | } 199 | else 200 | { 201 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 202 | } 203 | Write-Verbose -Message ($NuGetOutput | out-string) 204 | Pop-Location 205 | } 206 | 207 | # Make sure that Cake has been installed. 208 | if (!(Test-Path $CAKE_EXE)) { 209 | Throw "Could not find Cake.exe at $CAKE_EXE" 210 | } 211 | 212 | # Start Cake 213 | Write-Host "Running build script..." 214 | Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" 215 | exit $LASTEXITCODE 216 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################################## 4 | # This is the Cake bootstrapper script for Linux and OS X. 5 | # This file was downloaded from https://github.com/cake-build/resources 6 | # Feel free to change this file to fit your needs. 7 | ########################################################################## 8 | 9 | # Define directories. 10 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 11 | TOOLS_DIR=$SCRIPT_DIR/tools 12 | NUGET_EXE=$TOOLS_DIR/nuget.exe 13 | CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe 14 | PACKAGES_CONFIG=$TOOLS_DIR/packages.config 15 | PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum 16 | 17 | # Define md5sum or md5 depending on Linux/OSX 18 | MD5_EXE= 19 | if [[ "$(uname -s)" == "Darwin" ]]; then 20 | MD5_EXE="md5 -r" 21 | else 22 | MD5_EXE="md5sum" 23 | fi 24 | 25 | # Define default arguments. 26 | SCRIPT="build.cake" 27 | TARGET="Default" 28 | CONFIGURATION="Release" 29 | VERBOSITY="verbose" 30 | DRYRUN= 31 | SHOW_VERSION=false 32 | SCRIPT_ARGUMENTS=() 33 | 34 | # Parse arguments. 35 | for i in "$@"; do 36 | case $1 in 37 | -s|--script) SCRIPT="$2"; shift ;; 38 | -t|--target) TARGET="$2"; shift ;; 39 | -c|--configuration) CONFIGURATION="$2"; shift ;; 40 | -v|--verbosity) VERBOSITY="$2"; shift ;; 41 | -d|--dryrun) DRYRUN="-dryrun" ;; 42 | --version) SHOW_VERSION=true ;; 43 | --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; 44 | *) SCRIPT_ARGUMENTS+=("$1") ;; 45 | esac 46 | shift 47 | done 48 | 49 | # Make sure the tools folder exist. 50 | if [ ! -d "$TOOLS_DIR" ]; then 51 | mkdir "$TOOLS_DIR" 52 | fi 53 | 54 | # Make sure that packages.config exist. 55 | if [ ! -f "$TOOLS_DIR/packages.config" ]; then 56 | echo "Downloading packages.config..." 57 | curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages 58 | if [ $? -ne 0 ]; then 59 | echo "An error occured while downloading packages.config." 60 | exit 1 61 | fi 62 | fi 63 | 64 | # Download NuGet if it does not exist. 65 | if [ ! -f "$NUGET_EXE" ]; then 66 | echo "Downloading NuGet..." 67 | curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe 68 | if [ $? -ne 0 ]; then 69 | echo "An error occured while downloading nuget.exe." 70 | exit 1 71 | fi 72 | fi 73 | 74 | # Restore tools from NuGet. 75 | pushd "$TOOLS_DIR" >/dev/null 76 | if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then 77 | find . -type d ! -name . | xargs rm -rf 78 | fi 79 | 80 | mono "$NUGET_EXE" install -ExcludeVersion 81 | if [ $? -ne 0 ]; then 82 | echo "Could not restore NuGet packages." 83 | exit 1 84 | fi 85 | 86 | $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 87 | 88 | popd >/dev/null 89 | 90 | # Make sure that Cake has been installed. 91 | if [ ! -f "$CAKE_EXE" ]; then 92 | echo "Could not find Cake.exe at '$CAKE_EXE'." 93 | exit 1 94 | fi 95 | 96 | # Start Cake 97 | if $SHOW_VERSION; then 98 | exec mono "$CAKE_EXE" -version 99 | else 100 | exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" 101 | fi -------------------------------------------------------------------------------- /cake.packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout/661faad38ad10bc7f8d5774c99cf989356fcbd4e/demo.gif -------------------------------------------------------------------------------- /iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | using Refractored.XamForms.PullToRefresh.iOS; 8 | 9 | namespace RefreshSample.iOS 10 | { 11 | [Register("AppDelegate")] 12 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 13 | { 14 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) 15 | { 16 | 17 | UINavigationBar.Appearance.BarTintColor = UIColor.FromRGB(43, 132, 211); //bar background 18 | UINavigationBar.Appearance.TintColor = UIColor.White; //Tint color of button items 19 | UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes() 20 | { 21 | Font = UIFont.FromName("HelveticaNeue-Light", (nfloat)20f), 22 | TextColor = UIColor.White 23 | }); 24 | 25 | global::Xamarin.Forms.Forms.Init(); 26 | 27 | PullToRefreshLayoutRenderer.Init(); 28 | 29 | LoadApplication(new App()); 30 | 31 | return base.FinishedLaunching(app, options); 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | RefreshSample 7 | CFBundleIdentifier 8 | com.refractored.refreshsample 9 | CFBundleShortVersionString 10 | 1.0 11 | CFBundleVersion 12 | 1.0 13 | LSRequiresIPhoneOS 14 | 15 | MinimumOSVersion 16 | 7.0 17 | UIDeviceFamily 18 | 19 | 1 20 | 2 21 | 22 | UILaunchStoryboardName 23 | LaunchScreen 24 | UIRequiredDeviceCapabilities 25 | 26 | armv7 27 | 28 | UISupportedInterfaceOrientations 29 | 30 | UIInterfaceOrientationPortrait 31 | UIInterfaceOrientationLandscapeLeft 32 | UIInterfaceOrientationLandscapeRight 33 | 34 | UISupportedInterfaceOrientations~ipad 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationPortraitUpsideDown 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | XSAppIconAssets 42 | Resources/Images.xcassets/AppIcons.appiconset 43 | UIStatusBarStyle 44 | UIStatusBarStyleLightContent 45 | UIViewControllerBasedStatusBarAppearance 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace RefreshSample.iOS 9 | { 10 | public class Application 11 | { 12 | // This is the main entry point of the application. 13 | static void Main(string[] args) 14 | { 15 | // if you want to use a different Application Delegate class from "AppDelegate" 16 | // you can specify it here. 17 | UIApplication.Main(args, null, "AppDelegate"); 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /iOS/RefreshSample.iOS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 7 | {D2E6E0DD-948A-425C-9947-970223689429} 8 | Exe 9 | RefreshSample.iOS 10 | Resources 11 | RefreshSampleiOS 12 | 13 | 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\iPhoneSimulator\Debug 20 | DEBUG;ENABLE_TEST_CLOUD; 21 | prompt 22 | 4 23 | false 24 | i386, x86_64 25 | None 26 | true 27 | iPhone Developer 28 | true 29 | 30 | 31 | true 32 | 33 | 34 | full 35 | true 36 | bin\iPhone\Release 37 | prompt 38 | 4 39 | false 40 | ARMv7, ARM64 41 | Entitlements.plist 42 | true 43 | iPhone Developer 44 | 45 | 46 | full 47 | true 48 | bin\iPhoneSimulator\Release 49 | prompt 50 | 4 51 | false 52 | i386 53 | None 54 | iPhone Developer 55 | 56 | 57 | true 58 | full 59 | false 60 | bin\iPhone\Debug 61 | DEBUG;ENABLE_TEST_CLOUD; 62 | prompt 63 | 4 64 | false 65 | ARMv7, ARM64 66 | Entitlements.plist 67 | true 68 | iPhone Developer 69 | true 70 | true 71 | true 72 | 73 | 74 | None 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | {10ab134c-d1b9-4589-8121-3ceb3d21d096} 87 | PullToRefresh 88 | 89 | 90 | {E8A3B142-4FDA-44D2-AC4C-2578150D4E27} 91 | RefreshSample 92 | 93 | 94 | 95 | 96 | false 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 4.3.0.991211 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "29x29", 5 | "scale": "1x", 6 | "idiom": "iphone" 7 | }, 8 | { 9 | "size": "29x29", 10 | "scale": "2x", 11 | "idiom": "iphone" 12 | }, 13 | { 14 | "size": "29x29", 15 | "scale": "3x", 16 | "idiom": "iphone" 17 | }, 18 | { 19 | "size": "40x40", 20 | "scale": "2x", 21 | "idiom": "iphone" 22 | }, 23 | { 24 | "size": "40x40", 25 | "scale": "3x", 26 | "idiom": "iphone" 27 | }, 28 | { 29 | "size": "57x57", 30 | "scale": "1x", 31 | "idiom": "iphone" 32 | }, 33 | { 34 | "size": "57x57", 35 | "scale": "2x", 36 | "idiom": "iphone" 37 | }, 38 | { 39 | "size": "60x60", 40 | "scale": "2x", 41 | "idiom": "iphone" 42 | }, 43 | { 44 | "size": "60x60", 45 | "scale": "3x", 46 | "idiom": "iphone" 47 | }, 48 | { 49 | "size": "29x29", 50 | "scale": "1x", 51 | "idiom": "ipad" 52 | }, 53 | { 54 | "size": "29x29", 55 | "scale": "2x", 56 | "idiom": "ipad" 57 | }, 58 | { 59 | "size": "40x40", 60 | "scale": "1x", 61 | "idiom": "ipad" 62 | }, 63 | { 64 | "size": "40x40", 65 | "scale": "2x", 66 | "idiom": "ipad" 67 | }, 68 | { 69 | "size": "50x50", 70 | "scale": "1x", 71 | "idiom": "ipad" 72 | }, 73 | { 74 | "size": "50x50", 75 | "scale": "2x", 76 | "idiom": "ipad" 77 | }, 78 | { 79 | "size": "72x72", 80 | "scale": "1x", 81 | "idiom": "ipad" 82 | }, 83 | { 84 | "size": "72x72", 85 | "scale": "2x", 86 | "idiom": "ipad" 87 | }, 88 | { 89 | "size": "76x76", 90 | "scale": "1x", 91 | "idiom": "ipad" 92 | }, 93 | { 94 | "size": "76x76", 95 | "scale": "2x", 96 | "idiom": "ipad" 97 | }, 98 | { 99 | "size": "120x120", 100 | "scale": "1x", 101 | "idiom": "car" 102 | } 103 | ], 104 | "info": { 105 | "version": 1, 106 | "author": "xcode" 107 | } 108 | } -------------------------------------------------------------------------------- /iOS/Resources/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | --------------------------------------------------------------------------------