├── .github └── dependabot.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── Sample ├── AppDelegate.cs ├── Entitlements.plist ├── Info.plist ├── Main.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ └── LaunchScreen.xib ├── Sample.csproj └── ViewController.cs ├── TZStackView.nuspec ├── TZStackView.sln ├── TZStackView ├── Alignment.cs ├── AnimationDelegate.cs ├── Distribution.cs ├── Properties │ └── AssemblyInfo.cs ├── SpacerView.cs ├── StackView.cs └── TZStackView.csproj ├── appveyor.yml ├── assets └── layout-example.png ├── build.cake ├── build.ps1 ├── build.sh ├── releasenotes.md └── tools └── packages.config /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | ## Android Auto-generated files 5 | Resource.designer.cs 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | build/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | 28 | # Visual Studo 2015 cache/options directory 29 | .vs/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Tt]est[Rr]esult* 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | *.exe 70 | *.bak 71 | *.cache 72 | *.lib 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding addin-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | *.ncrunchproject 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | packages/ 155 | !packages/Microsoft.Office.js.1.0.0/ 156 | !packages/repositories.config 157 | 158 | #Allow NuGet.exe (do not ignore) 159 | !NuGet.exe 160 | *.orig 161 | 162 | # Windows Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | 169 | # Others 170 | *.[Cc]ache 171 | ClientBin/ 172 | [Ss]tyle[Cc]op.* 173 | ~$* 174 | *~ 175 | *.dbmdl 176 | *.dbproj.schemaview 177 | *.pfx 178 | *.publishsettings 179 | node_modules/ 180 | bower_components/ 181 | 182 | # RIA/Silverlight projects 183 | Generated_Code/ 184 | 185 | # Backup & report files from converting an old project file 186 | # to a newer Visual Studio version. Backup files are not needed, 187 | # because we have git ;-) 188 | _UpgradeReport_Files/ 189 | Backup*/ 190 | UpgradeLog*.XML 191 | UpgradeLog*.htm 192 | 193 | # SQL Server files 194 | *.mdf 195 | *.ldf 196 | 197 | # Business Intelligence projects 198 | *.rdl.data 199 | *.bim.layout 200 | *.bim_*.settings 201 | 202 | # Microsoft Fakes 203 | FakesAssemblies/ 204 | 205 | # Node.js Tools for Visual Studio 206 | .ntvs_analysis.dat 207 | 208 | # Visual Studio 6 build log 209 | *.plg 210 | 211 | # Visual Studio 6 workspace options file 212 | *.opt 213 | 214 | #ignore thumbnails created by windows 215 | Thumbs.db 216 | 217 | # Xcode 218 | .DS_Store 219 | */build/* 220 | *.pbxuser 221 | !default.pbxuser 222 | *.mode1v3 223 | !default.mode1v3 224 | *.mode2v3 225 | !default.mode2v3 226 | *.perspectivev3 227 | !default.perspectivev3 228 | xcuserdata 229 | profile 230 | *.moved-aside 231 | DerivedData 232 | .idea/ 233 | *.hmap 234 | *.xccheckout 235 | 236 | #CocoaPods 237 | Pods 238 | nuspec/nuget.exe 239 | 240 | #Build 241 | project.lock.json 242 | tools/Cake/ 243 | tools/Addins/ 244 | tools/gitlink/ 245 | tools/GitVersion.CommandLine/ 246 | tools/nuget.exe 247 | tools/packages.config.md5sum 248 | artifacts/ 249 | *.nupkg 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tom van Zummeren 4 | Copyright (c) 2016 Tomasz Cielecki 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The code in this project was ported from Swift to Xamarin.iOS by Tomasz Cielecki (tomasz@ostebaronen.dk) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TZStackView 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/rhogj0gqtl5fpkmj?svg=true)](https://ci.appveyor.com/project/Cheesebaron/tzstackview) 4 | 5 | This is a port of [Tom van Zummeren's][tvz] [TZStackView][tzstackview], which provides a backport of UIStackView, which was introduced in iOS 9. 6 | 7 | StackView provides an easy way to lay out views horizontally and vertically and UIStackView is the new recommended way to lay out things in iOS 9, 8 | instead of resolving to use explicit view constraints. 9 | 10 | ## Features 11 | - Compatible with iOS 7 and 8 12 | - Complete API of `UIStackView` with all distribution and alignment options 13 | - Animation of hidden property 14 | 15 | - Support for storyboards. 16 | 17 | ## Installing 18 | Add the TZStackView nuget to your Xamarin.iOS project 19 | 20 | > Install-Package TZStackView 21 | 22 | ## Usage 23 | Given `view1`, `view2` and `view3` have intrinsic content sizes set to 100x100, 80x80 and 60x60 respectively. 24 | 25 | ``` 26 | var stackView = new StackView(new UIView[] {view1, view2, view3}) 27 | { 28 | Axis = UILayoutConstraintAxis.Vertical, 29 | Distribution = Distribution.FillEqually, 30 | Alignment = Alignment.Center, 31 | Spacing = 25 32 | }; 33 | ``` 34 | 35 | This will produce the following layout. 36 | ![Layout Example][layout] 37 | 38 | To animate adding or removing a view from the arranged subviews simply toggle the `Hidden` property on the view. 39 | 40 | ``` 41 | UIView.AnimateNotify(0.6, 0, 0.7f, 0, UIViewAnimationOptions.AllowUserInteraction, 42 | () => { view.Hidden = true; }, completed => { }); 43 | ``` 44 | 45 | ## Migrating to UIStackView 46 | If you at some point want to make iOS 9 the minimum target of your application, you will want to replace this with `UIStackView`. 47 | Since `TZStackView` is a drop in replacement of `UIStackView`, you should be able to just use `UIStackView` instead. 48 | 49 | ``` 50 | var stackView = StackView(subViews); 51 | ``` 52 | 53 | ``` 54 | var stackView = UIStackView(subViews); 55 | ``` 56 | 57 | ## License 58 | TZStackView is licensed under the MIT License. See the [LICENSE](/LICENSE) file for details. 59 | 60 | [tvz]: https://github.com/tomvanzummeren 61 | [tzstackview]: https://github.com/tomvanzummeren/TZStackView 62 | [layout]: /assets/layout-example.png 63 | -------------------------------------------------------------------------------- /Sample/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | using UIKit; 3 | 4 | namespace Sample 5 | { 6 | // The UIApplicationDelegate for the application. This class is responsible for launching the 7 | // User Interface of the application, as well as listening (and optionally responding) to application events from iOS. 8 | [Register("AppDelegate")] 9 | public class AppDelegate : UIApplicationDelegate 10 | { 11 | // class-level declarations 12 | 13 | public override UIWindow Window 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) 20 | { 21 | // create a new window instance based on the screen size 22 | Window = new UIWindow(UIScreen.MainScreen.Bounds); 23 | 24 | UISegmentedControl.Appearance.SetTitleTextAttributes (new UITextAttributes () { 25 | Font = UIFont.FromName ("HelveticaNeue-Light", 10f) 26 | }, UIControlState.Normal); 27 | 28 | Window.RootViewController = new ViewController (); 29 | Window.MakeKeyAndVisible(); 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sample/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | Sample 7 | CFBundleIdentifier 8 | com.companyname.Sample 9 | CFBundleShortVersionString 10 | 1.0 11 | CFBundleVersion 12 | 1.0 13 | LSRequiresIPhoneOS 14 | 15 | MinimumOSVersion 16 | 9.2 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 | UIMainNibFile 42 | LaunchScreen 43 | 44 | 45 | -------------------------------------------------------------------------------- /Sample/Main.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace Sample 4 | { 5 | public class Application 6 | { 7 | // This is the main entry point of the application. 8 | static void Main(string[] args) 9 | { 10 | // if you want to use a different Application Delegate class from "AppDelegate" 11 | // you can specify it here. 12 | UIApplication.Main(args, null, "AppDelegate"); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Sample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sample")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("150d8a38-4419-4330-a42b-12879acb7a54")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Sample/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 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | {150D8A38-4419-4330-A42B-12879ACB7A54} 7 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | Exe 9 | Sample 10 | Resources 11 | Sample 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\iPhoneSimulator\Debug 18 | DEBUG 19 | prompt 20 | 4 21 | false 22 | i386 23 | None 24 | true 25 | 26 | 27 | none 28 | true 29 | bin\iPhoneSimulator\Release 30 | prompt 31 | 4 32 | None 33 | i386 34 | false 35 | 36 | 37 | true 38 | full 39 | false 40 | bin\iPhone\Debug 41 | DEBUG 42 | prompt 43 | 4 44 | false 45 | ARMv7, ARM64 46 | Entitlements.plist 47 | iPhone Developer 48 | true 49 | 50 | 51 | none 52 | true 53 | bin\iPhone\Release 54 | prompt 55 | 4 56 | Entitlements.plist 57 | ARMv7, ARM64 58 | false 59 | iPhone Developer 60 | 61 | 62 | none 63 | True 64 | bin\iPhone\Ad-Hoc 65 | prompt 66 | 4 67 | False 68 | ARMv7, ARM64 69 | Entitlements.plist 70 | True 71 | Automatic:AdHoc 72 | iPhone Distribution 73 | 74 | 75 | none 76 | True 77 | bin\iPhone\AppStore 78 | prompt 79 | 4 80 | False 81 | ARMv7, ARM64 82 | Entitlements.plist 83 | Automatic:AppStore 84 | iPhone Distribution 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F} 104 | TZStackView 105 | False 106 | False 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Sample/ViewController.cs: -------------------------------------------------------------------------------- 1 | using CoreGraphics; 2 | using Foundation; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using TZStackView; 7 | using UIKit; 8 | 9 | namespace Sample 10 | { 11 | public class ViewController : UIViewController 12 | { 13 | private StackView _stackView; 14 | 15 | private UIButton _resetButton = new UIButton(UIButtonType.System); 16 | private UILabel _instructionLabel = new UILabel(); 17 | 18 | private UISegmentedControl _axisSegmentedControl; 19 | private UISegmentedControl _alignmentSegmentedControl; 20 | private UISegmentedControl _distributionSegmentedControl; 21 | 22 | public override void ViewDidLoad() 23 | { 24 | base.ViewDidLoad(); 25 | EdgesForExtendedLayout = UIRectEdge.None; 26 | 27 | View.BackgroundColor = UIColor.Black; 28 | Title = "TZStackView"; 29 | 30 | _stackView = new StackView(CreateViews()) 31 | { 32 | TranslatesAutoresizingMaskIntoConstraints = false, 33 | Axis = UILayoutConstraintAxis.Vertical, 34 | Distribution = Distribution.Fill, 35 | Alignment = Alignment.Fill, 36 | Spacing = 15 37 | }; 38 | Add(_stackView); 39 | 40 | _instructionLabel.TranslatesAutoresizingMaskIntoConstraints = false; 41 | _instructionLabel.Font = UIFont.SystemFontOfSize(15); 42 | _instructionLabel.Text = "Tap any of the boxes to set Hidden=true"; 43 | _instructionLabel.TextColor = UIColor.White; 44 | _instructionLabel.Lines = 0; 45 | _instructionLabel.SetContentCompressionResistancePriority(900, UILayoutConstraintAxis.Horizontal); 46 | _instructionLabel.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical); 47 | Add(_instructionLabel); 48 | 49 | _resetButton.TranslatesAutoresizingMaskIntoConstraints = false; 50 | _resetButton.SetTitle("Reset", UIControlState.Normal); 51 | _resetButton.TouchUpInside += (_, __) => Reset(); 52 | _resetButton.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Horizontal); 53 | _resetButton.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Horizontal); 54 | _resetButton.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical); 55 | Add(_resetButton); 56 | 57 | _axisSegmentedControl = new UISegmentedControl (); 58 | _axisSegmentedControl.InsertSegment ("Vertical", 0, false); 59 | _axisSegmentedControl.InsertSegment ("Horizontal", 1, false); 60 | _axisSegmentedControl.SelectedSegment = 0; 61 | _axisSegmentedControl.ValueChanged += (s, __) => AxisChanged(s as UISegmentedControl); 62 | _axisSegmentedControl.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Horizontal); 63 | _axisSegmentedControl.TintColor = UIColor.LightGray; 64 | 65 | _alignmentSegmentedControl = new UISegmentedControl (); 66 | _alignmentSegmentedControl.InsertSegment ("Fill", 0, false); 67 | _alignmentSegmentedControl.InsertSegment ("Center", 1, false); 68 | _alignmentSegmentedControl.InsertSegment ("Leading", 2, false); 69 | _alignmentSegmentedControl.InsertSegment ("Top", 3, false); 70 | _alignmentSegmentedControl.InsertSegment ("Trailing", 4, false); 71 | _alignmentSegmentedControl.InsertSegment ("Bottom", 5, false); 72 | _alignmentSegmentedControl.InsertSegment ("FirstBaseline", 6, false); 73 | _alignmentSegmentedControl.SelectedSegment = 0; 74 | _alignmentSegmentedControl.ValueChanged += (s, __) => AlignmentChanged(s as UISegmentedControl); 75 | _alignmentSegmentedControl.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Horizontal); 76 | _alignmentSegmentedControl.TintColor = UIColor.LightGray; 77 | 78 | _distributionSegmentedControl = new UISegmentedControl (); 79 | _distributionSegmentedControl.InsertSegment ("Fill", 0, false); 80 | _distributionSegmentedControl.InsertSegment ("FillEqually", 1, false); 81 | _distributionSegmentedControl.InsertSegment ("FillProportionally", 2, false); 82 | _distributionSegmentedControl.InsertSegment ("EqualSpacing", 3, false); 83 | _distributionSegmentedControl.InsertSegment ("EqualCentering", 4, false); 84 | _distributionSegmentedControl.SelectedSegment = 0; 85 | _distributionSegmentedControl.ValueChanged += (s, __) => DistributionChanged(s as UISegmentedControl); 86 | _distributionSegmentedControl.TintColor = UIColor.LightGray; 87 | 88 | var controlsLayoutContainer = new StackView(new UIView[] 89 | { 90 | _axisSegmentedControl, _alignmentSegmentedControl, _distributionSegmentedControl 91 | }) 92 | { 93 | Axis = UILayoutConstraintAxis.Vertical, 94 | TranslatesAutoresizingMaskIntoConstraints = false, 95 | Spacing = 5 96 | }; 97 | controlsLayoutContainer.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical); 98 | Add(controlsLayoutContainer); 99 | 100 | var views = new NSMutableDictionary 101 | { 102 | {new NSString("instructionLabel"), _instructionLabel }, 103 | {new NSString("resetButton"), _resetButton }, 104 | {new NSString("stackView"), _stackView }, 105 | {new NSString("controlsLayoutContainer"), controlsLayoutContainer } 106 | }; 107 | 108 | var metrics = new NSMutableDictionary 109 | { 110 | {new NSString("gap"), FromObject(10) }, 111 | {new NSString("topspacing"), FromObject(25) } 112 | }; 113 | 114 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|-gap-[instructionLabel]-[resetButton]-gap-|", 115 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views)); 116 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|[stackView]|", 117 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views)); 118 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|[controlsLayoutContainer]|", 119 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views)); 120 | 121 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ( 122 | "V:|-topspacing-[instructionLabel]-gap-[controlsLayoutContainer]-gap-[stackView]|", 123 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views)); 124 | View.AddConstraints (NSLayoutConstraint.FromVisualFormat ( 125 | "V:|-topspacing-[resetButton]-gap-[controlsLayoutContainer]", 126 | NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics, views)); 127 | 128 | View.SetNeedsLayout (); 129 | } 130 | 131 | private IEnumerable CreateViews() 132 | { 133 | var redView = new ExplicitIntrinsicContentSizeView(new CGSize(100, 100), "Red"); 134 | var greenView = new ExplicitIntrinsicContentSizeView(new CGSize(80, 80), "Green"); 135 | var blueView = new ExplicitIntrinsicContentSizeView(new CGSize(60, 60), "Blue"); 136 | var purpleView = new ExplicitIntrinsicContentSizeView(new CGSize(80, 80), "Purple"); 137 | var yellowView = new ExplicitIntrinsicContentSizeView(new CGSize(100, 100), "Yellow"); 138 | 139 | redView.BackgroundColor = UIColor.Red.ColorWithAlpha(0.75f); 140 | greenView.BackgroundColor = UIColor.Green.ColorWithAlpha(0.75f); 141 | blueView.BackgroundColor = UIColor.Blue.ColorWithAlpha(0.75f); 142 | purpleView.BackgroundColor = UIColor.Purple.ColorWithAlpha(0.75f); 143 | yellowView.BackgroundColor = UIColor.Yellow.ColorWithAlpha(0.75f); 144 | 145 | return new[] { redView, greenView, blueView, purpleView, yellowView }; 146 | } 147 | 148 | private void Reset() 149 | { 150 | UIView.AnimateNotify(0.6, 0, 0.7f, 0, UIViewAnimationOptions.AllowUserInteraction, 151 | () => { 152 | foreach (var view in _stackView.ArrangedSubviews) 153 | view.Hidden = false; 154 | }, 155 | completed => { }); 156 | } 157 | 158 | private void AxisChanged(UISegmentedControl sender) 159 | { 160 | if (sender == null) return; 161 | 162 | _stackView.Axis = sender.SelectedSegment == 0 ? 163 | UILayoutConstraintAxis.Vertical : UILayoutConstraintAxis.Horizontal; 164 | } 165 | 166 | private void AlignmentChanged(UISegmentedControl sender) 167 | { 168 | if (sender == null) return; 169 | var index = sender.SelectedSegment; 170 | 171 | switch(index) { 172 | case 0: 173 | _stackView.Alignment = Alignment.Fill; 174 | break; 175 | case 1: 176 | _stackView.Alignment = Alignment.Center; 177 | break; 178 | case 2: 179 | _stackView.Alignment = Alignment.Leading; 180 | break; 181 | case 3: 182 | _stackView.Alignment = Alignment.Top; 183 | break; 184 | case 4: 185 | _stackView.Alignment = Alignment.Trailing; 186 | break; 187 | case 5: 188 | _stackView.Alignment = Alignment.Bottom; 189 | break; 190 | default: 191 | _stackView.Alignment = Alignment.FirstBaseline; 192 | break; 193 | } 194 | } 195 | 196 | private void DistributionChanged(UISegmentedControl sender) 197 | { 198 | if (sender == null) return; 199 | var index = sender.SelectedSegment; 200 | 201 | switch (index) { 202 | case 0: 203 | _stackView.Distribution = Distribution.Fill; 204 | break; 205 | case 1: 206 | _stackView.Distribution = Distribution.FillEqualy; 207 | break; 208 | case 2: 209 | _stackView.Distribution = Distribution.FillProportionally; 210 | break; 211 | case 3: 212 | _stackView.Distribution = Distribution.EqualSpacing; 213 | break; 214 | default: 215 | _stackView.Distribution = Distribution.EqualCentering; 216 | break; 217 | } 218 | } 219 | 220 | public override UIStatusBarStyle PreferredStatusBarStyle() { 221 | return UIStatusBarStyle.LightContent; 222 | } 223 | } 224 | 225 | public class ExplicitIntrinsicContentSizeView : UIView 226 | { 227 | private string _name; 228 | private CGSize _contentSize; 229 | 230 | public ExplicitIntrinsicContentSizeView(CGSize intrinsicContentSize, string name) : base(CGRect.Empty) 231 | { 232 | _name = name; 233 | _contentSize = intrinsicContentSize; 234 | 235 | var gestureRecognizer = new UITapGestureRecognizer(Tap); 236 | AddGestureRecognizer(gestureRecognizer); 237 | UserInteractionEnabled = true; 238 | } 239 | 240 | private void Tap() 241 | { 242 | UIView.AnimateNotify(0.6, 0, 0.7f, 0, UIViewAnimationOptions.AllowUserInteraction, 243 | () => { 244 | Hidden = true; 245 | }, 246 | completed => { }); 247 | } 248 | 249 | public override CGSize IntrinsicContentSize 250 | { 251 | get { return _contentSize; } 252 | } 253 | 254 | public override string Description 255 | { 256 | get { return _name; } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /TZStackView.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TZStackView 5 | UIStackView backport for iOS 7 and 8 for Xamarin.iOS 6 | 7 | -------------------------------------------------------------------------------- /TZStackView.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TZStackView", "TZStackView\TZStackView.csproj", "{EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{150D8A38-4419-4330-A42B-12879ACB7A54}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EE34CC8E-4EF8-4C74-922A-4E6B1113F70D}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | TZStackView.nuspec = TZStackView.nuspec 14 | build.cake = build.cake 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Ad-Hoc|Any CPU = Ad-Hoc|Any CPU 20 | Ad-Hoc|iPhone = Ad-Hoc|iPhone 21 | Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator 22 | AppStore|Any CPU = AppStore|Any CPU 23 | AppStore|iPhone = AppStore|iPhone 24 | AppStore|iPhoneSimulator = AppStore|iPhoneSimulator 25 | Debug|Any CPU = Debug|Any CPU 26 | Debug|iPhone = Debug|iPhone 27 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 28 | Release|Any CPU = Release|Any CPU 29 | Release|iPhone = Release|iPhone 30 | Release|iPhoneSimulator = Release|iPhoneSimulator 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|Any CPU.ActiveCfg = AppStore|Any CPU 34 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|Any CPU.Build.0 = AppStore|Any CPU 35 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU 36 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU 37 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = AppStore|Any CPU 38 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Ad-Hoc|iPhoneSimulator.Build.0 = AppStore|Any CPU 39 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU 40 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|Any CPU.Build.0 = AppStore|Any CPU 41 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhone.ActiveCfg = Debug|Any CPU 42 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhone.Build.0 = Debug|Any CPU 43 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|Any CPU 44 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.AppStore|iPhoneSimulator.Build.0 = AppStore|Any CPU 45 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhone.ActiveCfg = Debug|Any CPU 48 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhone.Build.0 = Debug|Any CPU 49 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 50 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 51 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhone.ActiveCfg = Release|Any CPU 54 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhone.Build.0 = Release|Any CPU 55 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 56 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 57 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhoneSimulator 58 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone 59 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone 60 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator 61 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator 62 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone 63 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|Any CPU.Build.0 = AppStore|iPhone 64 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhone.ActiveCfg = AppStore|iPhone 65 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhone.Build.0 = AppStore|iPhone 66 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator 67 | {150D8A38-4419-4330-A42B-12879ACB7A54}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator 68 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 69 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 70 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhone.ActiveCfg = Debug|iPhone 71 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhone.Build.0 = Debug|iPhone 72 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 73 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 74 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator 75 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|Any CPU.Build.0 = Release|iPhoneSimulator 76 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhone.ActiveCfg = Release|iPhone 77 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhone.Build.0 = Release|iPhone 78 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 79 | {150D8A38-4419-4330-A42B-12879ACB7A54}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 80 | EndGlobalSection 81 | GlobalSection(SolutionProperties) = preSolution 82 | HideSolutionNode = FALSE 83 | EndGlobalSection 84 | EndGlobal 85 | -------------------------------------------------------------------------------- /TZStackView/Alignment.cs: -------------------------------------------------------------------------------- 1 | namespace TZStackView 2 | { 3 | /// 4 | /// Alignment, the layout transverse to the stacking axis. 5 | /// 6 | public enum Alignment 7 | { 8 | /// 9 | /// Align the leading and trailing edges of vertically stacked items 10 | /// or the top and bottom edges of horizontally stacked items tightly to the container. 11 | /// 12 | Fill = 0, 13 | 14 | /// 15 | /// Align the leading edges of vertically stacked items 16 | /// or the top edges of horizontally stacked items tightly to the relevant edge 17 | /// of the container. 18 | /// 19 | Leading = 1, 20 | 21 | /// 22 | /// Align the leading edges of vertically stacked items 23 | /// or the top edges of horizontally stacked items tightly to the relevant edge 24 | /// of the container. 25 | /// 26 | Top = Leading, 27 | 28 | FirstBaseline = 2, 29 | 30 | /// 31 | /// Center the items in a vertical stack horizontally 32 | /// or the items in a horizontal stack vertically 33 | /// 34 | Center = 3, 35 | 36 | /// 37 | /// Align the trailing edges of vertically stacked items 38 | /// or the bottom edges of horizontally stacked items tightly to the relevant 39 | /// edge of the container 40 | /// 41 | Trailing = 4, 42 | 43 | /// 44 | /// Align the trailing edges of vertically stacked items 45 | /// or the bottom edges of horizontally stacked items tightly to the relevant 46 | /// edge of the container 47 | /// 48 | Bottom = Trailing 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /TZStackView/AnimationDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreAnimation; 3 | 4 | namespace TZStackView 5 | { 6 | public class AnimationDelegate : CAAnimationDelegate 7 | { 8 | public Action AnimationStoppedCallback { get; set;} 9 | 10 | public override void AnimationStopped (CAAnimation anim, bool finished) 11 | { 12 | AnimationStoppedCallback?.Invoke (); 13 | } 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /TZStackView/Distribution.cs: -------------------------------------------------------------------------------- 1 | namespace TZStackView 2 | { 3 | /// 4 | /// Distribution. The layout along the stacking axis. 5 | /// All enum values fit first and last 6 | /// arranged subviews tightly to the container, except for 7 | /// , fit all items to 8 | /// when possible. 9 | /// 10 | public enum Distribution 11 | { 12 | /// 13 | /// When items do not fit (overflow) or fill (underflow) the space available 14 | /// adjustments occur according to CompressionResistance or hugging 15 | /// priorities of items, or when that is ambiguous, according to arrangement order. 16 | /// 17 | Fill = 0, 18 | 19 | /// 20 | /// Items are all the same size. 21 | /// When space allows, this will be the size of the item with the largest 22 | /// (along the axis of the stack). 23 | /// Overflow or underflow adjustments are distributed equally among the items. 24 | /// 25 | FillEqualy = 1, 26 | 27 | /// 28 | /// Overflow or underflow adjustments are distributed among the items proportional 29 | /// to their . 30 | /// 31 | FillProportionally = 2, 32 | 33 | /// 34 | /// Additional underflow spacing is divided equally in the spaces between the items. 35 | /// Overflow squeezing is controlled by compressionResistance priorities followed by 36 | /// arrangement order. 37 | /// 38 | EqualSpacing = 3, 39 | 40 | /// 41 | /// Equal center-to-center spacing of the items is maintained as much 42 | /// as possible while still maintaining a minimum edge-to-edge spacing within the 43 | /// allowed area. 44 | /// Additional underflow spacing is divided equally in the spacing. Overflow 45 | /// squeezing is distributed first according to CompressionResistance priorities 46 | /// of items, then according to subview order while maintaining the configured 47 | /// (edge-to-edge) spacing as a minimum. 48 | /// 49 | EqualCentering = 4 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /TZStackView/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TZStackView")] 8 | [assembly: AssemblyDescription("UIStackView backport for iOS 7 & 8 for Xamarin.iOS")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Tomasz Cielecki")] 11 | [assembly: AssemblyProduct("TZStackView")] 12 | [assembly: AssemblyCopyright("Copyright © Tomasz Cielecki (@cheesebaron) 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("efb28af7-e0ff-4957-82f8-0113d9bbfd8f")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | [assembly: AssemblyInformationalVersion("0.0.2")] 37 | -------------------------------------------------------------------------------- /TZStackView/SpacerView.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace TZStackView 4 | { 5 | public class SpacerView : UIView 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /TZStackView/StackView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Collections.Specialized; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using Foundation; 8 | using UIKit; 9 | using CoreGraphics; 10 | using CoreAnimation; 11 | 12 | namespace TZStackView 13 | { 14 | [Register("TZStackView")] 15 | [DesignTimeVisible(true)] 16 | public class StackView : UIView 17 | { 18 | private readonly ObservableCollection _arrangedSubviews = 19 | new ObservableCollection(); 20 | private readonly List _registeredHiddenObserverViews = new List(); 21 | private readonly List _animatingToHiddenViews = new List(); 22 | private readonly List _spacerViews = new List(); 23 | private readonly List _stackViewConstraints = 24 | new List(); 25 | private readonly List _subviewConstraints = 26 | new List(); 27 | private Alignment _alignment = Alignment.Fill; 28 | private Distribution _distribution = Distribution.Fill; 29 | private UILayoutConstraintAxis _axis = UILayoutConstraintAxis.Horizontal; 30 | 31 | public Distribution Distribution 32 | { 33 | get { return _distribution; } 34 | set 35 | { 36 | _distribution = value; 37 | SetNeedsUpdateConstraints(); 38 | } 39 | } 40 | 41 | [Export("Disribution"), Browsable(true)] 42 | public int DistributionValue 43 | { 44 | get { return (int)Distribution; } 45 | set { Distribution = (Distribution)value; } 46 | } 47 | 48 | public Alignment Alignment 49 | { 50 | get { return _alignment; } 51 | set 52 | { 53 | _alignment = value; 54 | SetNeedsUpdateConstraints(); 55 | } 56 | } 57 | 58 | [Export("Alignment"), Browsable(true)] 59 | public int AlignmentValue 60 | { 61 | get { return (int)Alignment; } 62 | set { Alignment = (Alignment)value; } 63 | } 64 | 65 | public UILayoutConstraintAxis Axis 66 | { 67 | get { return _axis; } 68 | set 69 | { 70 | _axis = value; 71 | SetNeedsUpdateConstraints(); 72 | } 73 | } 74 | 75 | [Export("Axis"), Browsable(true)] 76 | public int AxisValue 77 | { 78 | get { return (int)Axis; } 79 | set { Axis = (UILayoutConstraintAxis)value; } 80 | } 81 | 82 | public IEnumerable ArrangedSubviews => _arrangedSubviews; 83 | 84 | [Export("LayoutMarginsRelative"), Browsable(true)] 85 | public bool LayoutMarginsRelativeArrangement { get; set; } = false; 86 | 87 | [Export("Spacing"), Browsable(true)] 88 | public float Spacing { get; set; } = 0f; 89 | 90 | private void ArrangedSubviewsChanged(object s, 91 | NotifyCollectionChangedEventArgs args) 92 | { 93 | var oldItems = args.OldItems; 94 | if (oldItems != null) 95 | foreach (var subview in oldItems.OfType()) 96 | RemoveHiddenListener(subview); 97 | 98 | var newItems = args.NewItems; 99 | if (newItems != null) 100 | foreach(var subview in newItems.OfType()) 101 | AddHiddenListener(subview); 102 | } 103 | 104 | public StackView(IntPtr handle) : base(handle) { } 105 | 106 | public StackView(IEnumerable arrangedSubviews = null) 107 | : base(CGRect.Empty) 108 | { 109 | _arrangedSubviews.CollectionChanged += ArrangedSubviewsChanged; 110 | Initialize(arrangedSubviews); 111 | } 112 | 113 | public StackView(CGRect frame) 114 | : base(frame) 115 | { 116 | _arrangedSubviews.CollectionChanged += ArrangedSubviewsChanged; 117 | Initialize(); 118 | } 119 | 120 | public override void AwakeFromNib() 121 | { 122 | foreach (var constraint in Constraints) 123 | { 124 | if (constraint.GetType().Name == "NSIBPrototypingLayoutConstraint") 125 | { 126 | RemoveConstraint(constraint); 127 | } 128 | } 129 | 130 | foreach (var view in Subviews) 131 | AddArrangedSubview(view); 132 | } 133 | 134 | public override void PrepareForInterfaceBuilder() 135 | { 136 | base.PrepareForInterfaceBuilder(); 137 | 138 | foreach (var view in Subviews) 139 | AddArrangedSubview(view); 140 | } 141 | 142 | private void Initialize(IEnumerable arrangedSubviews = null) 143 | { 144 | if (arrangedSubviews == null) 145 | arrangedSubviews = new List(); 146 | 147 | foreach (var subview in arrangedSubviews) 148 | { 149 | subview.TranslatesAutoresizingMaskIntoConstraints = false; 150 | AddSubview(subview); 151 | _arrangedSubviews.Add(subview); 152 | } 153 | } 154 | 155 | private void AddHiddenListener(UIView view) 156 | { 157 | view.Layer.AddObserver(this, "hidden", NSKeyValueObservingOptions.OldNew, IntPtr.Zero); 158 | _registeredHiddenObserverViews.Add(view); 159 | } 160 | 161 | private void RemoveHiddenListener(UIView view) 162 | { 163 | if (_registeredHiddenObserverViews.Contains(view)) 164 | { 165 | view.Layer.RemoveObserver(this, (NSString)"hidden", IntPtr.Zero); 166 | _registeredHiddenObserverViews.Remove(view); 167 | } 168 | } 169 | 170 | public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, 171 | IntPtr context) 172 | { 173 | if (keyPath != "hidden") return; 174 | 175 | var layer = ofObject as CALayer; 176 | 177 | // Delegate/WeakDelegate is usually the UIView on CALayer 178 | var view = layer?.WeakDelegate as UIView; 179 | if (view == null) 180 | return; 181 | 182 | var hidden = view.Hidden; 183 | var previousValue = (change["old"] as NSNumber)?.BoolValue; 184 | if (previousValue.HasValue && 185 | previousValue.Value == hidden) 186 | { 187 | return; 188 | } 189 | 190 | if (hidden) 191 | { 192 | _animatingToHiddenViews.Add(view); 193 | } 194 | 195 | SetNeedsUpdateConstraints(); 196 | SetNeedsLayout(); 197 | LayoutIfNeeded(); 198 | 199 | RemoveHiddenListener(view); 200 | //view.Hidden = false; 201 | 202 | var hidingAnimation = layer.AnimationForKey ("bounds.size"); 203 | 204 | Action animationFinished = () => { 205 | var strongLayer = layer; 206 | strongLayer.Hidden = hidden; 207 | var strongView = view; 208 | if (_animatingToHiddenViews.Contains(strongView)){ 209 | _animatingToHiddenViews.Remove(strongView); 210 | } 211 | AddHiddenListener(strongView); 212 | }; 213 | 214 | if (hidingAnimation != null) 215 | { 216 | var group = new CAAnimationGroup (); 217 | group.Animations = new CAAnimation[0]; 218 | group.Delegate = new AnimationDelegate { 219 | AnimationStoppedCallback = animationFinished 220 | }; 221 | 222 | layer.AddAnimation (group, "TZSV-hidden-callback"); 223 | } 224 | else{ 225 | animationFinished (); 226 | } 227 | } 228 | 229 | public void AddArrangedSubview(UIView view) 230 | { 231 | view.TranslatesAutoresizingMaskIntoConstraints = false; 232 | AddSubview(view); 233 | _arrangedSubviews.Add(view); 234 | } 235 | 236 | public void RemoveArrangedSubview(UIView view) 237 | { 238 | if (_arrangedSubviews.Contains(view)) 239 | _arrangedSubviews.Remove(view); 240 | } 241 | 242 | public void InsertArrangedSubview(UIView view, int atIndex) 243 | { 244 | view.TranslatesAutoresizingMaskIntoConstraints = false; 245 | AddSubview(view); 246 | _arrangedSubviews.Insert(atIndex, view); 247 | } 248 | 249 | public override void WillRemoveSubview(UIView view) 250 | { 251 | RemoveArrangedSubview(view); 252 | } 253 | 254 | public override void UpdateConstraints() 255 | { 256 | RemoveConstraints(_stackViewConstraints.ToArray()); 257 | _stackViewConstraints.Clear(); 258 | 259 | foreach (var arrangedSubview in _arrangedSubviews) 260 | arrangedSubview.RemoveConstraints(_subviewConstraints.ToArray()); 261 | _subviewConstraints.Clear(); 262 | 263 | foreach (var arrangedSubview in _arrangedSubviews) 264 | { 265 | if (Alignment != Alignment.Fill) 266 | { 267 | NSLayoutConstraint guideConstraint = null; 268 | if (Axis == UILayoutConstraintAxis.Horizontal) 269 | { 270 | guideConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Height, 271 | view2: null, attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 25); 272 | } 273 | else if (Axis == UILayoutConstraintAxis.Vertical) 274 | { 275 | guideConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Width, 276 | view2: null, attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 25); 277 | } 278 | 279 | _subviewConstraints.Add(guideConstraint); 280 | arrangedSubview.AddConstraint(guideConstraint); 281 | } 282 | 283 | if (IsHidden(arrangedSubview)) 284 | { 285 | NSLayoutConstraint hiddenConstraint = null; 286 | if (Axis == UILayoutConstraintAxis.Horizontal) 287 | { 288 | hiddenConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Width, 289 | view2: null, attr2: NSLayoutAttribute.NoAttribute); 290 | } 291 | else if (Axis == UILayoutConstraintAxis.Vertical) 292 | { 293 | hiddenConstraint = Constraint(arrangedSubview, NSLayoutAttribute.Height, 294 | view2: null, attr2: NSLayoutAttribute.NoAttribute); 295 | } 296 | _subviewConstraints.Add(hiddenConstraint); 297 | arrangedSubview.AddConstraint(hiddenConstraint); 298 | } 299 | } 300 | 301 | foreach(var spacerView in _spacerViews) 302 | spacerView.RemoveFromSuperview(); 303 | _spacerViews.Clear(); 304 | 305 | if (_arrangedSubviews.Any()) 306 | { 307 | var visibleArrangedSubviews = _arrangedSubviews.Where(v => !IsHidden(v)).ToArray(); 308 | 309 | switch (Distribution) 310 | { 311 | case Distribution.Fill: 312 | case Distribution.FillEqualy: 313 | case Distribution.FillProportionally: { 314 | if (Alignment != Alignment.Fill || LayoutMarginsRelativeArrangement) 315 | AddSpacerView(); 316 | 317 | _stackViewConstraints.AddRange(CreateMatchEdgesConstraints(_arrangedSubviews)); 318 | _stackViewConstraints.AddRange(CreateFirstAndLastViewMatchEdgesConstraints()); 319 | 320 | if (Alignment == Alignment.FirstBaseline && 321 | Axis == UILayoutConstraintAxis.Horizontal) 322 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height, 323 | attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 324 | 325 | if (Distribution == Distribution.FillEqualy) 326 | _stackViewConstraints.AddRange(CreateFillEquallyConstraints(_arrangedSubviews)); 327 | if (Distribution == Distribution.FillProportionally) 328 | _stackViewConstraints.AddRange( 329 | CreateFillProportionallyConstraints(_arrangedSubviews)); 330 | 331 | _stackViewConstraints.AddRange(CreateFillConstraints(_arrangedSubviews, 332 | constant: Spacing)); 333 | break; 334 | } 335 | case Distribution.EqualSpacing: { 336 | var views = new List(); 337 | var index = 0; 338 | foreach (var arrangedSubview in _arrangedSubviews) 339 | { 340 | if (IsHidden(arrangedSubview)) 341 | continue; 342 | if (index > 0) 343 | views.Add(AddSpacerView()); 344 | views.Add(arrangedSubview); 345 | index++; 346 | } 347 | if (_spacerViews.Count == 0) 348 | AddSpacerView(); 349 | 350 | _stackViewConstraints.AddRange(CreateMatchEdgesConstraints(_arrangedSubviews)); 351 | _stackViewConstraints.AddRange(CreateFirstAndLastViewMatchEdgesConstraints()); 352 | 353 | if (Axis == UILayoutConstraintAxis.Horizontal) 354 | { 355 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Width, 356 | attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 357 | if (Alignment == Alignment.FirstBaseline) 358 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height, 359 | attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 360 | } 361 | else if (Axis == UILayoutConstraintAxis.Vertical) 362 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height, attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 363 | 364 | _stackViewConstraints.AddRange(CreateFillConstraints(views)); 365 | _stackViewConstraints.AddRange(CreateFillEquallyConstraints(_spacerViews)); 366 | _stackViewConstraints.AddRange(CreateFillConstraints(_arrangedSubviews, 367 | relatedBy: NSLayoutRelation.GreaterThanOrEqual, constant: Spacing)); 368 | break; 369 | } 370 | case Distribution.EqualCentering: { 371 | for (var i = 0; i < visibleArrangedSubviews.Length; i++) 372 | if (i > 0) AddSpacerView(); 373 | 374 | if (_spacerViews.Count == 0) 375 | AddSpacerView(); 376 | 377 | _stackViewConstraints.AddRange(CreateMatchEdgesConstraints(_arrangedSubviews)); 378 | _stackViewConstraints.AddRange(CreateFirstAndLastViewMatchEdgesConstraints()); 379 | 380 | if (Axis == UILayoutConstraintAxis.Horizontal) 381 | { 382 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Width, 383 | attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 384 | if (Alignment == Alignment.FirstBaseline) 385 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height, 386 | attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 387 | } 388 | else if (Axis == UILayoutConstraintAxis.Vertical) 389 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.Height, 390 | attr2: NSLayoutAttribute.NoAttribute, priority: 49)); 391 | 392 | UIView previousArrangedSubview = null; 393 | var index = 0; 394 | foreach (var arrangedSubview in visibleArrangedSubviews) 395 | { 396 | if (previousArrangedSubview != null) 397 | { 398 | var spacerView = _spacerViews[index - 1]; 399 | 400 | if (Axis == UILayoutConstraintAxis.Horizontal) 401 | { 402 | _stackViewConstraints.Add(Constraint(previousArrangedSubview, 403 | NSLayoutAttribute.CenterX, view2: spacerView, 404 | attr2: NSLayoutAttribute.Leading)); 405 | _stackViewConstraints.Add(Constraint(arrangedSubview, 406 | NSLayoutAttribute.CenterX, view2: spacerView, 407 | attr2: NSLayoutAttribute.Trailing)); 408 | } 409 | else if (Axis == UILayoutConstraintAxis.Vertical) 410 | { 411 | _stackViewConstraints.Add(Constraint(previousArrangedSubview, 412 | NSLayoutAttribute.CenterY, view2: spacerView, 413 | attr2: NSLayoutAttribute.Top)); 414 | _stackViewConstraints.Add(Constraint(arrangedSubview, 415 | NSLayoutAttribute.CenterY, view2: spacerView, 416 | attr2: NSLayoutAttribute.Bottom)); 417 | } 418 | } 419 | 420 | previousArrangedSubview = arrangedSubview; 421 | index++; 422 | } 423 | 424 | _stackViewConstraints.AddRange(CreateFillEquallyConstraints(_spacerViews, 425 | priority: 150)); 426 | _stackViewConstraints.AddRange(CreateFillConstraints(_arrangedSubviews, 427 | relatedBy: NSLayoutRelation.GreaterThanOrEqual, constant: Spacing)); 428 | break; 429 | } 430 | } 431 | 432 | if (_spacerViews.Any()) 433 | _stackViewConstraints.AddRange(CreateSurroundingSpacerViewConstraints( 434 | _spacerViews[0], views: visibleArrangedSubviews)); 435 | 436 | if (LayoutMarginsRelativeArrangement && _spacerViews.Any()) 437 | { 438 | var first = _spacerViews[0]; 439 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.BottomMargin, view2: first)); 440 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.LeftMargin, view2: first)); 441 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.TopMargin, view2: first)); 442 | _stackViewConstraints.Add(Constraint(this, NSLayoutAttribute.RightMargin, view2: first)); 443 | } 444 | AddConstraints(_stackViewConstraints.ToArray()); 445 | } 446 | 447 | base.UpdateConstraints(); 448 | } 449 | 450 | private SpacerView AddSpacerView() 451 | { 452 | var spacerView = new SpacerView {TranslatesAutoresizingMaskIntoConstraints = false}; 453 | 454 | _spacerViews.Add(spacerView); 455 | InsertSubview(spacerView, 0); 456 | 457 | return spacerView; 458 | } 459 | 460 | private IEnumerable CreateSurroundingSpacerViewConstraints(UIView spacerView, 461 | IEnumerable views) 462 | { 463 | if (Alignment == Alignment.Fill) 464 | return new NSLayoutConstraint[0]; 465 | 466 | var topPriority = 1000f; 467 | var topRelation = NSLayoutRelation.LessThanOrEqual; 468 | 469 | var bottomPriority = 1000f; 470 | var bottomRelation = NSLayoutRelation.GreaterThanOrEqual; 471 | 472 | if (Alignment == Alignment.Top || Alignment == Alignment.Leading) 473 | { 474 | topPriority = 999.5f; 475 | topRelation = NSLayoutRelation.Equal; 476 | } 477 | 478 | if (Alignment == Alignment.Bottom || Alignment == Alignment.Trailing) 479 | { 480 | bottomPriority = 999.5f; 481 | bottomRelation = NSLayoutRelation.Equal; 482 | } 483 | 484 | var constraints = new List(); 485 | foreach (var view in views) 486 | { 487 | if (Axis == UILayoutConstraintAxis.Horizontal) 488 | { 489 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Top, topRelation, view, 490 | priority: topPriority)); 491 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Bottom, bottomRelation, view, 492 | priority: bottomPriority)); 493 | } 494 | else if (Axis == UILayoutConstraintAxis.Vertical) 495 | { 496 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Leading, topRelation, view, 497 | priority: topPriority)); 498 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Trailing, bottomRelation, view, 499 | priority: bottomPriority)); 500 | } 501 | } 502 | 503 | if (Axis == UILayoutConstraintAxis.Horizontal) 504 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Height, 505 | attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 51)); 506 | else if (Axis == UILayoutConstraintAxis.Vertical) 507 | constraints.Add(Constraint(spacerView, NSLayoutAttribute.Width, 508 | attr2: NSLayoutAttribute.NoAttribute, constant: 0, priority: 51)); 509 | 510 | return constraints; 511 | } 512 | 513 | private IEnumerable CreateFillProportionallyConstraints( 514 | IEnumerable views) 515 | { 516 | var viewss = views.ToArray(); 517 | var constraints = new List(); 518 | 519 | nfloat totalSize = 0f; 520 | var totalCount = 0; 521 | 522 | foreach (var subview in viewss) 523 | { 524 | if (IsHidden(subview)) continue; 525 | 526 | if (Axis == UILayoutConstraintAxis.Horizontal) 527 | totalSize += subview.IntrinsicContentSize.Width; 528 | else if (Axis == UILayoutConstraintAxis.Vertical) 529 | totalSize += subview.IntrinsicContentSize.Height; 530 | totalCount++; 531 | } 532 | 533 | totalSize += (totalCount - 1) * Spacing; 534 | 535 | if (totalSize <= 0) 536 | totalSize = 1; 537 | 538 | var priority = 1000f; 539 | var countDownPriority = viewss.Count(v => !IsHidden(v)) > 1; 540 | 541 | foreach (var subview in viewss) 542 | { 543 | if (countDownPriority) 544 | priority--; 545 | 546 | if (IsHidden(subview)) continue; 547 | 548 | if (Axis == UILayoutConstraintAxis.Horizontal) 549 | { 550 | var multiplier = subview.IntrinsicContentSize.Width/totalSize; 551 | constraints.Add(Constraint(subview, NSLayoutAttribute.Width, 552 | multiplier: (float) multiplier, priority: priority, view2: this)); 553 | } 554 | else if (Axis == UILayoutConstraintAxis.Vertical) 555 | { 556 | var multiplier = subview.IntrinsicContentSize.Height / totalSize; 557 | constraints.Add(Constraint(subview, NSLayoutAttribute.Height, 558 | multiplier: (float)multiplier, priority: priority, view2: this)); 559 | } 560 | } 561 | 562 | return constraints; 563 | } 564 | 565 | private IEnumerable CreateFillEquallyConstraints( 566 | IEnumerable views, float priority = 1000) 567 | { 568 | if (Axis == UILayoutConstraintAxis.Horizontal) 569 | return EqualAttributes(views.Where(v => !IsHidden(v)), attribute: NSLayoutAttribute.Width, priority: priority); 570 | return EqualAttributes(views.Where(v => !IsHidden(v)), attribute: NSLayoutAttribute.Height, priority: priority); 571 | } 572 | 573 | private IEnumerable CreateFillConstraints( 574 | IEnumerable views, float priority = 1000, NSLayoutRelation relatedBy = NSLayoutRelation.Equal, float constant = 0) 575 | { 576 | var constraints = new List(); 577 | 578 | var viewss = views.ToArray(); 579 | 580 | UIView previousView = null; 581 | foreach(var view in viewss) 582 | { 583 | if (previousView != null) 584 | { 585 | var c = 0f; 586 | if (!IsHidden(previousView) && !IsHidden(view)) 587 | c = constant; 588 | else if (IsHidden(previousView) && !IsHidden(view) && !Equals(viewss.FirstOrDefault(), previousView)) 589 | c = (constant / 2f); 590 | else if (!IsHidden(previousView) && IsHidden(view) && !Equals(viewss.LastOrDefault(), view)) 591 | c = (constant / 2f); 592 | 593 | if (Axis == UILayoutConstraintAxis.Horizontal) 594 | constraints.Add(Constraint(view, NSLayoutAttribute.Leading, relatedBy, previousView, NSLayoutAttribute.Trailing, constant: c, priority: priority)); 595 | else if (Axis == UILayoutConstraintAxis.Vertical) 596 | constraints.Add(Constraint(view, NSLayoutAttribute.Top, relatedBy, previousView, 597 | NSLayoutAttribute.Bottom, constant: c, priority: priority)); 598 | } 599 | previousView = view; 600 | } 601 | 602 | return constraints; 603 | } 604 | 605 | private IEnumerable CreateMatchEdgesConstraints( 606 | IEnumerable views) 607 | { 608 | var constraints = new List(); 609 | 610 | if (Axis == UILayoutConstraintAxis.Horizontal) 611 | { 612 | if (Alignment == Alignment.Fill) 613 | { 614 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Bottom)); 615 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Top)); 616 | } 617 | else if (Alignment == Alignment.Center) 618 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.CenterY)); 619 | else if (Alignment == Alignment.Leading || Alignment == Alignment.Top) 620 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Top)); 621 | else if (Alignment == Alignment.Trailing || Alignment == Alignment.Bottom) 622 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Bottom)); 623 | else if (Alignment == Alignment.FirstBaseline) 624 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.FirstBaseline)); 625 | } 626 | else if (Axis == UILayoutConstraintAxis.Vertical) 627 | { 628 | if (Alignment == Alignment.Fill) 629 | { 630 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Leading)); 631 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Trailing)); 632 | } 633 | else if (Alignment == Alignment.Center) 634 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.CenterX)); 635 | else if (Alignment == Alignment.Leading || Alignment == Alignment.Top) 636 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Leading)); 637 | else if (Alignment == Alignment.Trailing || Alignment == Alignment.Bottom) 638 | constraints.AddRange(EqualAttributes(views, NSLayoutAttribute.Trailing)); 639 | } 640 | 641 | return constraints; 642 | } 643 | 644 | private IEnumerable CreateFirstAndLastViewMatchEdgesConstraints() 645 | { 646 | var constraints = new List(); 647 | 648 | var visibleViews = _arrangedSubviews.Where(v => !IsHidden(v)).ToArray(); 649 | var firstView = visibleViews.FirstOrDefault(); 650 | var lastView = visibleViews.LastOrDefault(); 651 | 652 | var topView = _arrangedSubviews.FirstOrDefault(); 653 | var bottomView = topView; 654 | 655 | 656 | if (_spacerViews.Any()) 657 | { 658 | var spacerView = _spacerViews[0]; 659 | if (Alignment == Alignment.Center) 660 | { 661 | topView = spacerView; 662 | bottomView = spacerView; 663 | } 664 | else if (Alignment == Alignment.Top || Alignment == Alignment.Leading) 665 | bottomView = spacerView; 666 | else if (Alignment == Alignment.Bottom || Alignment == Alignment.Trailing) 667 | topView = spacerView; 668 | else if (Alignment == Alignment.FirstBaseline) 669 | { 670 | if (Axis == UILayoutConstraintAxis.Horizontal) 671 | bottomView = spacerView; 672 | else if (Axis == UILayoutConstraintAxis.Vertical) 673 | { 674 | topView = spacerView; 675 | bottomView = spacerView; 676 | } 677 | } 678 | } 679 | 680 | var firstItem = LayoutMarginsRelativeArrangement ? _spacerViews.FirstOrDefault() : this; 681 | 682 | if (Axis == UILayoutConstraintAxis.Horizontal) 683 | { 684 | if (firstView != null) 685 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Leading, view2: firstView)); 686 | 687 | if (lastView != null) 688 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Trailing, view2: lastView)); 689 | 690 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Top, view2: topView)); 691 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Bottom, view2: bottomView)); 692 | 693 | if (Alignment == Alignment.Center) 694 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.CenterY, view2: _arrangedSubviews.First())); 695 | } 696 | else if (Axis == UILayoutConstraintAxis.Vertical) 697 | { 698 | if (firstView != null) 699 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Top, view2: firstView)); 700 | 701 | if (lastView != null) 702 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Bottom, view2: lastView)); 703 | 704 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Leading, view2: topView)); 705 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.Trailing, view2: bottomView)); 706 | 707 | if (Alignment == Alignment.Center) 708 | constraints.Add(Constraint(firstItem, NSLayoutAttribute.CenterX, view2: _arrangedSubviews.First())); 709 | } 710 | 711 | return constraints; 712 | } 713 | 714 | private IEnumerable EqualAttributes(IEnumerable views, NSLayoutAttribute attribute, 715 | float priority = 1000) 716 | { 717 | var currentPriority = priority; 718 | var constraints = new List(); 719 | 720 | var viewss = views.ToArray(); 721 | 722 | if (views != null && viewss.Any()) 723 | { 724 | UIView firstView = null; 725 | var countDownPriority = currentPriority < 1000; 726 | foreach(var view in viewss) 727 | { 728 | if (firstView != null) 729 | constraints.Add(Constraint(firstView, attribute, view2: view, priority: currentPriority)); 730 | else 731 | firstView = view; 732 | 733 | if (countDownPriority) 734 | currentPriority--; 735 | } 736 | } 737 | 738 | return constraints; 739 | } 740 | 741 | private NSLayoutConstraint Constraint(NSObject view1, NSLayoutAttribute attr1, 742 | NSLayoutRelation relation = NSLayoutRelation.Equal, NSObject view2 = null, 743 | NSLayoutAttribute? attr2 = null, float multiplier = 1, float constant = 0, 744 | float priority = 1000) 745 | { 746 | var attribute2 = attr2 ?? attr1; 747 | 748 | var constraint = NSLayoutConstraint.Create(view1, attr1, relation, view2, attribute2, 749 | multiplier, constant); 750 | constraint.Priority = priority; 751 | return constraint; 752 | } 753 | 754 | private bool IsHidden(UIView view) 755 | { 756 | if (view.Hidden) 757 | return true; 758 | return _animatingToHiddenViews.IndexOf(view) >= 0; 759 | } 760 | } 761 | } 762 | -------------------------------------------------------------------------------- /TZStackView/TZStackView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {EFB28AF7-E0FF-4957-82F8-0113D9BBFD8F} 7 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | Library 9 | TZStackView 10 | Resources 11 | TZStackView 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug 18 | DEBUG; 19 | prompt 20 | 4 21 | false 22 | 23 | 24 | full 25 | true 26 | bin\Release 27 | prompt 28 | 4 29 | false 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | nuget: 3 | project_feed: true 4 | disable_publish_on_pr: true 5 | build_script: 6 | - ps: .\build.ps1 7 | deploy: 8 | - provider: NuGet 9 | api_key: 10 | secure: M6ahLYqDdpC2Whn/9Q/VU9AMb8P0rcz87+rmHDLYfi9Bl9gzGHA1vwL6Xh4DTfGR 11 | on: 12 | appveyor_repo_tag: true -------------------------------------------------------------------------------- /assets/layout-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/TZStackView/6c887cac2cfd9fcefdf56260d263ad2ac5d25f5b/assets/layout-example.png -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #tool "nuget:?package=GitVersion.CommandLine" 2 | #tool "nuget:?package=gitlink" 3 | 4 | var sln = new FilePath("TZStackView.sln"); 5 | var project = new FilePath("TZStackView/TZStackView.csproj"); 6 | var binDir = new DirectoryPath("TZStackView/bin/Release"); 7 | var nuspec = new FilePath("TZStackView.nuspec"); 8 | var outputDir = new DirectoryPath("artifacts"); 9 | var target = Argument("target", "Default"); 10 | 11 | var isRunningOnAppVeyor = AppVeyor.IsRunningOnAppVeyor; 12 | var isPullRequest = AppVeyor.Environment.PullRequest.IsPullRequest; 13 | 14 | Task("Clean").Does(() => 15 | { 16 | CleanDirectories("./**/bin"); 17 | CleanDirectories("./**/obj"); 18 | CleanDirectories(outputDir.FullPath); 19 | }); 20 | 21 | GitVersion versionInfo = null; 22 | Task("Version").Does(() => { 23 | GitVersion(new GitVersionSettings { 24 | UpdateAssemblyInfo = true, 25 | OutputType = GitVersionOutput.BuildServer 26 | }); 27 | 28 | versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); 29 | Information("VI:\t{0}", versionInfo.FullSemVer); 30 | }); 31 | 32 | Task("Restore").Does(() => { 33 | NuGetRestore(sln); 34 | }); 35 | 36 | Task("Build") 37 | .IsDependentOn("Clean") 38 | .IsDependentOn("Version") 39 | .IsDependentOn("Restore") 40 | .Does(() => { 41 | 42 | DotNetBuild(project, 43 | settings => settings.SetConfiguration("Release") 44 | .WithTarget("Build") 45 | ); 46 | }); 47 | 48 | Task("Package") 49 | .IsDependentOn("Build") 50 | .Does(() => { 51 | if (IsRunningOnWindows()) //pdbstr.exe and costura are not xplat currently 52 | GitLink(sln.GetDirectory(), new GitLinkSettings { 53 | RepositoryUrl = "https://github.com/Cheesebaron/TZStackView", 54 | ArgumentCustomization = args => args.Append("-ignore Sample") 55 | }); 56 | 57 | EnsureDirectoryExists(outputDir); 58 | 59 | var files = GetFiles(binDir + "/**/*") 60 | - GetFiles(binDir + "/**/*.mdb"); // no mdb files please 61 | 62 | var nugetContent = new List(); 63 | foreach(var dll in files){ 64 | Information("File: {0}", dll.ToString()); 65 | nugetContent.Add(new NuSpecContent { 66 | Target = "lib/Xamarin.iOS10", 67 | Source = dll.ToString() 68 | }); 69 | } 70 | 71 | Information("File Count {0}", nugetContent.Count); 72 | 73 | NuGetPack(nuspec, new NuGetPackSettings { 74 | Authors = new [] { "Tomasz Cielecki" }, 75 | Owners = new [] { "Tomasz Cielecki" }, 76 | IconUrl = new Uri("http://i.imgur.com/V3983YY.png"), 77 | ProjectUrl = new Uri("https://github.com/Cheesebaron/TZStackView"), 78 | LicenseUrl = new Uri("https://github.com/Cheesebaron/TZStackView/blob/master/LICENSE"), 79 | Copyright = "Copyright (c) Tomasz Cielecki", 80 | RequireLicenseAcceptance = false, 81 | Tags = new [] {"xamarin", "monotouch", "ios", "stackview"}, 82 | Version = versionInfo.NuGetVersion, 83 | Symbols = false, 84 | NoPackageAnalysis = true, 85 | OutputDirectory = outputDir, 86 | Verbosity = NuGetVerbosity.Detailed, 87 | Files = nugetContent, 88 | BasePath = "/.", 89 | ReleaseNotes = ParseReleaseNotes("./releasenotes.md").Notes.ToArray() 90 | }); 91 | }); 92 | 93 | Task("UploadAppVeyorArtifact") 94 | .IsDependentOn("Package") 95 | .WithCriteria(() => isRunningOnAppVeyor) 96 | .Does(() => { 97 | 98 | Information("Artifacts Dir: {0}", outputDir.FullPath); 99 | 100 | foreach(var file in GetFiles(outputDir.FullPath + "/*")) { 101 | Information("Uploading {0}", file.FullPath); 102 | AppVeyor.UploadArtifact(file.FullPath); 103 | } 104 | }); 105 | 106 | Task("Default").IsDependentOn("UploadAppVeyorArtifact").Does(() => {}); 107 | 108 | RunTarget(target); -------------------------------------------------------------------------------- /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 $_) } 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 | # Save nuget.exe path to environment to be available to child processed 152 | $ENV:NUGET_EXE = $NUGET_EXE 153 | 154 | # Restore tools from NuGet? 155 | if(-Not $SkipToolPackageRestore.IsPresent) { 156 | Push-Location 157 | Set-Location $TOOLS_DIR 158 | 159 | # Check for changes in packages.config and remove installed tools if true. 160 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 161 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 162 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 163 | Write-Verbose -Message "Missing or changed package.config hash..." 164 | Remove-Item * -Recurse -Exclude packages.config,nuget.exe 165 | } 166 | 167 | Write-Verbose -Message "Restoring tools from NuGet..." 168 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 169 | 170 | if ($LASTEXITCODE -ne 0) { 171 | Throw "An error occured while restoring NuGet tools." 172 | } 173 | else 174 | { 175 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 176 | } 177 | Write-Verbose -Message ($NuGetOutput | out-string) 178 | Pop-Location 179 | } 180 | 181 | # Make sure that Cake has been installed. 182 | if (!(Test-Path $CAKE_EXE)) { 183 | Throw "Could not find Cake.exe at $CAKE_EXE" 184 | } 185 | 186 | # Start Cake 187 | Write-Host "Running build script..." 188 | Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" 189 | exit $LASTEXITCODE -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /releasenotes.md: -------------------------------------------------------------------------------- 1 | ### New in 1.1.0 2 | - Fixed XIB/Storyboard support 3 | - Aligned enums with UIKit 4 | 5 | ### New in 1.1.1 (released 26/3/2017) 6 | - Fixed FillProportionally Distribution ([#7](https://github.com/Cheesebaron/TZStackView/pull/7)) -------------------------------------------------------------------------------- /tools/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --------------------------------------------------------------------------------