ios - एक्सकोड 6 में ऑटोलाउट बाधाओं का उपयोग करके पहलू-फिट व्यवहार को अनुकरण करना




objective-c xcode (5)

मैं ऑटोलायआउट का उपयोग आकार और लेआउट को इस तरह से देखना चाहता हूं जो UIImageView के पहलू-फिट सामग्री मोड की याद दिलाता है।

इंटरफेस बिल्डर में कंटेनर व्यू के अंदर मेरे पास एक सबव्यूव है। सबव्यूव में कुछ अंतर्निहित पहलू अनुपात है जिसका मैं सम्मान करना चाहता हूं। कंटेनर व्यू का आकार रनटाइम तक अज्ञात है।

यदि कंटेनर व्यू का पहलू अनुपात सबव्यू से बड़ा है, तो मैं सबव्यू की ऊंचाई को मूल दृश्य की ऊंचाई के बराबर करना चाहता हूं।

यदि कंटेनर व्यू का पहलू अनुपात सबव्यू से लंबा है, तो मैं सबव्यू की चौड़ाई को मूल दृश्य की चौड़ाई के बराबर करना चाहता हूं।

किसी भी मामले में मैं चाहता हूं कि सबवेव कंटेनर व्यू के भीतर क्षैतिज और लंबवत केंद्रित हो।

क्या एक्सकोड 6 या पिछले संस्करण में ऑटोलाउट बाधाओं का उपयोग करके इसे हासिल करने का कोई तरीका है? आदर्श रूप से इंटरफ़ेस बिल्डर का उपयोग करते हुए, लेकिन यदि संभवतः प्रोग्रामिंग रूप से ऐसी बाधाओं को परिभाषित करना संभव नहीं है।


आप स्केल-टू-फिट का वर्णन नहीं कर रहे हैं; आप पहलू फिट का वर्णन कर रहे हैं। (मैंने इस संबंध में आपका प्रश्न संपादित कर लिया है।) सबूत अपने पहलू अनुपात को बनाए रखने और अपने माता-पिता के अंदर पूरी तरह फिट करने के दौरान जितना संभव हो उतना बड़ा हो जाता है।

वैसे भी, आप ऑटो लेआउट के साथ ऐसा कर सकते हैं। आप इसे पूरी तरह से आईबी में एक्सकोड 5.1 के रूप में कर सकते हैं। आइए कुछ विचारों से शुरू करें:

हल्के हरे रंग के दृश्य में 4: 1 का पहलू अनुपात होता है। गहरे हरे रंग के दृश्य में 1: 4 का पहलू अनुपात होता है। मैं बाधाओं को स्थापित करने जा रहा हूं ताकि नीली दृश्य स्क्रीन के शीर्ष आधे भाग को भर सके, गुलाबी दृश्य स्क्रीन के निचले हिस्से को भरता है, और प्रत्येक हरे रंग का दृश्य जितना संभव हो उतना विस्तार करता है जबकि इसके पहलू अनुपात को बनाए रखता है और इसमें फिट होता है कंटेनर।

सबसे पहले, मैं नीले रंग के दृश्य के चारों तरफ बाधाएं पैदा करूंगा। मैं 0 की दूरी के साथ, प्रत्येक किनारे पर अपने निकटतम पड़ोसी को पिन कर दूंगा। मैं मार्जिन को बंद करना सुनिश्चित करता हूं:

ध्यान दें कि मैं अभी तक फ्रेम को अद्यतन नहीं करता हूं। बाधाओं को स्थापित करते समय मुझे विचारों के बीच कमरे छोड़ना आसान लगता है, और केवल स्थिरांक को हाथ से 0 (या जो कुछ भी) पर सेट करें।

इसके बाद, मैं अपने निकटतम पड़ोसी को गुलाबी दृश्य के बाएं, नीचे और दाएं किनारों को पिन करता हूं। मुझे एक शीर्ष किनारे की बाधा स्थापित करने की आवश्यकता नहीं है क्योंकि इसका शीर्ष किनारा नीले रंग के निचले किनारे पर पहले ही सीमित है।

मुझे गुलाबी और नीले विचारों के बीच एक समान ऊंचाई की बाधा की भी आवश्यकता है। यह उन्हें प्रत्येक आधा स्क्रीन भर देगा:

यदि मैं अब सभी फ्रेम को अपडेट करने के लिए एक्सकोड बताता हूं, तो मुझे यह मिलता है:

तो अब तक स्थापित बाधाएं सही हैं। मैं इसे पूर्ववत करता हूं और हल्के हरे रंग के दृश्य पर काम शुरू करता हूं।

प्रकाश हरे रंग के दृश्य को समझने के लिए पांच बाधाओं की आवश्यकता होती है:

  • हल्के हरे रंग के दृश्य पर एक आवश्यक प्राथमिकता पहलू अनुपात बाधा। आप xcode 5.1 या बाद में xib या स्टोरीबोर्ड में इस बाधा को बना सकते हैं।
  • एक आवश्यक प्राथमिकता बाधा प्रकाश हरे रंग के दृश्य की चौड़ाई को सीमित करने के लिए इसके कंटेनर की चौड़ाई से कम या बराबर होती है।
  • प्रकाश कंटेनर की चौड़ाई के बराबर होने के लिए हल्के हरे रंग के दृश्य की चौड़ाई को स्थापित करने वाली उच्च प्राथमिकता बाधा।
  • एक आवश्यक प्राथमिकता बाधा प्रकाश हरे रंग के दृश्य की ऊंचाई को सीमित करने के लिए इसके कंटेनर की ऊंचाई से कम या बराबर होती है।
  • हल्के हरे रंग के दृश्य की ऊंचाई को अपने कंटेनर की ऊंचाई के बराबर करने के लिए एक उच्च प्राथमिकता बाधा।

आइए दो चौड़ाई बाधाओं पर विचार करें। कम से कम या बराबर बाधा, अपने आप से, हल्के हरे रंग के दृश्य की चौड़ाई निर्धारित करने के लिए पर्याप्त नहीं है; कई चौड़ाई बाधा फिट होगी। चूंकि अस्पष्टता है, इसलिए ऑटोलाउट एक ऐसे समाधान को चुनने का प्रयास करेगा जो दूसरे (उच्च प्राथमिकता लेकिन आवश्यक नहीं) बाधा में त्रुटि को कम करता है। त्रुटि को कम करने का अर्थ है चौड़ाई को जितना संभव हो सके कंटेनर की चौड़ाई के लिए बनाना, जबकि आवश्यक कम-से-बराबर बाधा का उल्लंघन नहीं करना।

ऊंचाई की बाधा के साथ एक ही बात होती है। और चूंकि पहलू-अनुपात की बाधा भी आवश्यक है, यह केवल एक धुरी के साथ सबव्यूव के आकार को अधिकतम कर सकती है (जब तक कि कंटेनर को सबव्यूव के समान पहलू अनुपात न हो)।

तो सबसे पहले मैं पहलू अनुपात बाधा उत्पन्न करता हूं:

फिर मैं कंटेनर के साथ समान चौड़ाई और ऊंचाई की बाधाएं बना देता हूं:

मुझे इन बाधाओं को कम से कम या बराबर बाधाओं को संपादित करने की आवश्यकता है:

इसके बाद मुझे कंटेनर के साथ समान चौड़ाई और ऊंचाई की बाधाओं का एक और सेट बनाना होगा:

और मुझे इन नई बाधाओं को आवश्यक प्राथमिकता से कम करने की आवश्यकता है:

अंत में, आपने सबवेव को अपने कंटेनर में केंद्रित करने के लिए कहा, इसलिए मैं उन बाधाओं को स्थापित करूंगा:

अब, परीक्षण करने के लिए, मैं व्यू कंट्रोलर का चयन करूंगा और सभी फ्रेम को अपडेट करने के लिए एक्सकोड से पूछूंगा। मुझे यही मिलता है:

ऊप्स! सबव्यूव अपने कंटेनर को पूरी तरह से भरने के लिए विस्तारित हुआ है। यदि मैं इसे चुनता हूं, तो मैं देख सकता हूं कि वास्तव में यह अपने पहलू अनुपात को बनाए रखता है, लेकिन यह एक पहलू के बजाय एक पहलू भर रहा है

समस्या यह है कि कम से कम या बराबर बाधा पर, यह महत्वपूर्ण है कि बाधा के प्रत्येक छोर पर कौन सा दृश्य है, और एक्सकोड ने मेरी अपेक्षा से विपरीत बाधा स्थापित की है। मैं प्रत्येक बाधाओं में से प्रत्येक का चयन कर सकता हूं और अपनी पहली और दूसरी वस्तुओं को उलट सकता हूं। इसके बजाए, मैं सिर्फ सबव्यूव का चयन करूंगा और बाधाओं को अधिक से अधिक या बराबर में बदल दूंगा:

एक्सकोड लेआउट अद्यतन करता है:

अब मैं नीचे की ओर गहरे हरे रंग के दृश्य के लिए एक ही चीजें करता हूं। मुझे यह सुनिश्चित करने की ज़रूरत है कि इसका पहलू अनुपात 1: 4 है (एक्सकोड ने इसे अजीब तरीके से आकार दिया क्योंकि इसमें बाधाएं नहीं थीं)। मैं फिर से कदम नहीं दिखाऊंगा क्योंकि वे वही हैं। परिणाम यहां दिया गया है:

अब मैं इसे आईफोन 4 एस सिम्युलेटर में चला सकता हूं, जिसमें आईबी की तुलना में एक अलग स्क्रीन आकार है, और परीक्षण रोटेशन:

और मैं आईफोन 6 सिम्युलेटर में परीक्षण कर सकता हूं:

मैंने आपकी सुविधा के लिए इस अंतिम कहानी को अपना अंतिम स्टोरीबोर्ड अपलोड कर लिया है।


मुझे स्वीकृत उत्तर से समाधान की आवश्यकता थी, लेकिन कोड से निष्पादित किया गया था। मैंने पाया सबसे सुंदर तरीका चिनाई ढांचे का उपयोग कर रहा है।

#import "Masonry.h"

...

[view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.width.equalTo(view.mas_height).multipliedBy(aspectRatio);
    make.size.lessThanOrEqualTo(superview);
    make.size.equalTo(superview).with.priorityHigh();
    make.center.equalTo(superview);
}];

यह NSLayoutAnchor ऑब्जेक्ट्स का उपयोग करके और NSLayoutAnchor को पोर्ट किया गया है, यह कोड-केंद्रित दृष्टिकोण के @ rob_mayoff के उत्कृष्ट उत्तर का एक बंदरगाह है। मेरे लिए, NSLayoutAnchor और संबंधित वर्गों ने NSLayoutAnchor को कार्यक्रम के लिए बहुत आसान बना दिया है:

public class ContentView : UIView
{
        public ContentView (UIColor fillColor)
        {
            BackgroundColor = fillColor;
        }
}

public class MyController : UIViewController 
{
    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        //Starting point:
        var view = new ContentView (UIColor.White);

        blueView = new ContentView (UIColor.FromRGB (166, 200, 255));
        view.AddSubview (blueView);

        lightGreenView = new ContentView (UIColor.FromRGB (200, 255, 220));
        lightGreenView.Frame = new CGRect (20, 40, 200, 60);

        view.AddSubview (lightGreenView);

        pinkView = new ContentView (UIColor.FromRGB (255, 204, 240));
        view.AddSubview (pinkView);

        greenView = new ContentView (UIColor.Green);
        greenView.Frame = new CGRect (80, 20, 40, 200);
        pinkView.AddSubview (greenView);

        //Now start doing in code the things that @rob_mayoff did in IB

        //Make the blue view size up to its parent, but half the height
        blueView.TranslatesAutoresizingMaskIntoConstraints = false;
        var blueConstraints = new []
        {
            blueView.LeadingAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.LeadingAnchor),
            blueView.TrailingAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.TrailingAnchor),
            blueView.TopAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.TopAnchor),
            blueView.HeightAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.HeightAnchor, (nfloat) 0.5)
        };
        NSLayoutConstraint.ActivateConstraints (blueConstraints);

        //Make the pink view same size as blue view, and linked to bottom of blue view
        pinkView.TranslatesAutoresizingMaskIntoConstraints = false;
        var pinkConstraints = new []
        {
            pinkView.LeadingAnchor.ConstraintEqualTo(blueView.LeadingAnchor),
            pinkView.TrailingAnchor.ConstraintEqualTo(blueView.TrailingAnchor),
            pinkView.HeightAnchor.ConstraintEqualTo(blueView.HeightAnchor),
            pinkView.TopAnchor.ConstraintEqualTo(blueView.BottomAnchor)
        };
        NSLayoutConstraint.ActivateConstraints (pinkConstraints);


        //From here, address the aspect-fitting challenge:

        lightGreenView.TranslatesAutoresizingMaskIntoConstraints = false;
        //These are the must-fulfill constraints: 
        var lightGreenConstraints = new []
        {
            //Aspect ratio of 1 : 5
            NSLayoutConstraint.Create(lightGreenView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, lightGreenView, NSLayoutAttribute.Width, (nfloat) 0.20, 0),
            //Cannot be larger than parent's width or height
            lightGreenView.WidthAnchor.ConstraintLessThanOrEqualTo(blueView.WidthAnchor),
            lightGreenView.HeightAnchor.ConstraintLessThanOrEqualTo(blueView.HeightAnchor),
            //Center in parent
            lightGreenView.CenterYAnchor.ConstraintEqualTo(blueView.CenterYAnchor),
            lightGreenView.CenterXAnchor.ConstraintEqualTo(blueView.CenterXAnchor)
        };
        //Must-fulfill
        foreach (var c in lightGreenConstraints) 
        {
            c.Priority = 1000;
        }
        NSLayoutConstraint.ActivateConstraints (lightGreenConstraints);

        //Low priority constraint to attempt to fill parent as much as possible (but lower priority than previous)
        var lightGreenLowPriorityConstraints = new []
         {
            lightGreenView.WidthAnchor.ConstraintEqualTo(blueView.WidthAnchor),
            lightGreenView.HeightAnchor.ConstraintEqualTo(blueView.HeightAnchor)
        };
        //Lower priority
        foreach (var c in lightGreenLowPriorityConstraints) 
        {
            c.Priority = 750;
        }

        NSLayoutConstraint.ActivateConstraints (lightGreenLowPriorityConstraints);

        //Aspect-fit on the green view now
        greenView.TranslatesAutoresizingMaskIntoConstraints = false;
        var greenConstraints = new []
        {
            //Aspect ratio of 5:1
            NSLayoutConstraint.Create(greenView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, greenView, NSLayoutAttribute.Width, (nfloat) 5.0, 0),
            //Cannot be larger than parent's width or height
            greenView.WidthAnchor.ConstraintLessThanOrEqualTo(pinkView.WidthAnchor),
            greenView.HeightAnchor.ConstraintLessThanOrEqualTo(pinkView.HeightAnchor),
            //Center in parent
            greenView.CenterXAnchor.ConstraintEqualTo(pinkView.CenterXAnchor),
            greenView.CenterYAnchor.ConstraintEqualTo(pinkView.CenterYAnchor)
        };
        //Must fulfill
        foreach (var c in greenConstraints) 
        {
            c.Priority = 1000;
        }
        NSLayoutConstraint.ActivateConstraints (greenConstraints);

        //Low priority constraint to attempt to fill parent as much as possible (but lower priority than previous)
        var greenLowPriorityConstraints = new []
        {
            greenView.WidthAnchor.ConstraintEqualTo(pinkView.WidthAnchor),
            greenView.HeightAnchor.ConstraintEqualTo(pinkView.HeightAnchor)
        };
        //Lower-priority than above
        foreach (var c in greenLowPriorityConstraints) 
        {
            c.Priority = 750;
        }

        NSLayoutConstraint.ActivateConstraints (greenLowPriorityConstraints);

        this.View = view;

        view.LayoutIfNeeded ();
    }
}

शायद यह Masonry साथ सबसे छोटा जवाब है।

[containerView addSubview:subview];

[subview mas_makeConstraints:^(MASConstraintMaker *make) {
    if (contentMode == ContentMode_scaleToFill) {
        make.edges.equalTo(containerView);
    }
    else {
        make.center.equalTo(containerView);
        make.edges.equalTo(containerView).priorityHigh();
        make.width.equalTo(content.mas_height).multipliedBy(4.0 / 3);
        if (contentMode == ContentMode_scaleAspectFit) {
            make.width.height.lessThanOrEqualTo(containerView);
        }
        else { // contentMode == ContentMode_scaleAspectFill
            make.width.height.greaterThanOrEqualTo(containerView);
        }
    }
}];

रोब, आपका जवाब कमाल है! मुझे यह भी पता है कि यह प्रश्न विशेष रूप से ऑटो-लेआउट का उपयोग करके इसे प्राप्त करने के बारे में है। हालांकि, एक संदर्भ के रूप में, मैं यह दिखाना चाहता हूं कि कोड में यह कैसे किया जा सकता है। रॉब ने दिखाए गए जैसे आप ऊपर और नीचे के दृश्य (नीले और गुलाबी) सेट अप करते हैं। फिर आप एक कस्टम AspectFitView :

AspectFitView.h :

#import <UIKit/UIKit.h>

@interface AspectFitView : UIView

@property (nonatomic, strong) UIView *childView;

@end

AspectFitView.m :

#import "AspectFitView.h"

@implementation AspectFitView

- (void)setChildView:(UIView *)childView
{
    if (_childView) {
        [_childView removeFromSuperview];
    }

    _childView = childView;

    [self addSubview:childView];
    [self setNeedsLayout];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (_childView) {
        CGSize childSize = _childView.frame.size;
        CGSize parentSize = self.frame.size;
        CGFloat aspectRatioForHeight = childSize.width / childSize.height;
        CGFloat aspectRatioForWidth = childSize.height / childSize.width;

        if ((parentSize.height * aspectRatioForHeight) > parentSize.height) {
            // whole height, adjust width
            CGFloat width = parentSize.width * aspectRatioForWidth;
            _childView.frame = CGRectMake((parentSize.width - width) / 2.0, 0, width, parentSize.height);
        } else {
            // whole width, adjust height
            CGFloat height = parentSize.height * aspectRatioForHeight;
            _childView.frame = CGRectMake(0, (parentSize.height - height) / 2.0, parentSize.width, height);
        }
    }
}

@end

इसके बाद, आप स्टोरीबोर्ड में नीले और गुलाबी दृश्यों की कक्षा को AspectFitView एस के रूप में AspectFitView । आखिर में आप अपने topAspectFitView और bottomAspectFitView topAspectFitView दो आउटलेट सेट करते हैं और अपने childView में childView :

- (void)viewDidLoad {
    [super viewDidLoad];

    UIView *top = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 100)];
    top.backgroundColor = [UIColor lightGrayColor];

    UIView *bottom = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 500)];
    bottom.backgroundColor = [UIColor greenColor];

    _topAspectFitView.childView = top;
    _bottomAspectFitView.childView = bottom;
}

तो कोड में ऐसा करना मुश्किल नहीं है और यह अभी भी बहुत अनुकूल है और विविध आकार के विचारों और विभिन्न पहलू अनुपात के साथ काम करता है।

जुलाई 2015 अपडेट करें: यहां एक डेमो ऐप ढूंढें: https://github.com/jfahrenkrug/SPWKAspectFitView





autolayout