ios - محاكاة السلوك الملائم للوجه باستخدام القيود AutoLayout في Xcode 6




objective-c (5)

أنت لا تصف مقياس الملاءمة ؛ كنت تصف الجانب المناسب. (لقد قمت بتحرير سؤالك في هذا الصدد.) يصبح العرض الفرعي كبيرًا قدر الإمكان مع الحفاظ على نسبة العرض إلى الارتفاع وملائمة تمامًا داخل الشركة الأم.

على أي حال ، يمكنك القيام بذلك باستخدام التخطيط التلقائي. يمكنك القيام بذلك بشكل كامل في IB من Xcode 5.1. لنبدأ ببعض المشاهدات:

يبلغ عرض اللون الأخضر الفاتح نسبة عرض إلى ارتفاع 4: 1. يبلغ عرض الأخضر الداكن نسبة عرض إلى ارتفاع 1: 4. سأقوم بإعداد قيود بحيث يملأ العرض الأزرق النصف العلوي من الشاشة ، ويملأ العرض الوردي النصف السفلي من الشاشة ، ويمتد كل عرض أخضر بأكبر قدر ممكن مع الحفاظ على نسبة العرض إلى الارتفاع وملائمة في حاوية.

أولاً ، سأقوم بإنشاء قيود على الجوانب الأربعة للرؤية الزرقاء. سوف أضعه على أقرب جار له على كل حافة ، بمسافة 0. أتأكد من إيقاف الهوامش:

لاحظ أنه لا يتم تحديث الإطار بعد. أجد أنه من الأسهل ترك مساحة بين وجهات النظر عند إعداد قيود ، وتعيين الثوابت فقط إلى 0 (أو أيا كان) باليد.

بعد ذلك ، أقوم بتثبيت الحواف اليسرى والأسفل واليمين من العرض الوردي إلى أقرب جار له. لا أحتاج إلى إعداد قيد أعلى الحافة لأن حده العلوي مقيَّد بالفعل على الحافة السفلية من العرض الأزرق.

أنا أيضًا بحاجة إلى قيود متساوية الارتفاع بين العرضين الوردي والأزرق. هذا سيجعل كل منهم يملأ نصف الشاشة:

إذا أخبرت Xcode بتحديث كل الإطارات الآن ، فأحصل على هذا:

لذا فإن القيود التي أعددتها حتى الآن صحيحة. أنا التراجع عن ذلك وبدء العمل على الضوء الأخضر.

تتطلب زاوية عرض الضوء الأخضر خمس قيود:

  • قيد نسبة أبعاد مطلوبة بالأولوية على العرض الأخضر الفاتح. يمكنك إنشاء هذا القيد في xib أو لوحة العمل مع Xcode 5.1 أو أحدث.
  • القيد ذو الأولوية المطلوبة الذي يحد من عرض العرض الأخضر الفاتح ليكون أقل من أو يساوي عرض الحاوية الخاصة به.
  • قيد ذو أولوية عالية يضبط عرض العرض الأخضر الفاتح ليكون مساوياً لعرض الحاوية الخاصة به.
  • القيد المطلوب للأولوية الذي يحد من ارتفاع عرض الضوء الأخضر ليكون أقل من أو يساوي ارتفاع الحاوية الخاصة به.
  • قيد ذو أولوية عالية يضبط ارتفاع العرض الأخضر الفاتح ليكون مساوياً لارتفاع الحاوية الخاصة به.

دعونا النظر في قيود العرض اثنين. القيد الأقل من أو يساوي ، بحد ذاته ، ليس كافياً لتحديد عرض المشهد الأخضر الفاتح ؛ العديد من العروض تناسب القيد. نظرًا لوجود غموض ، سيحاول autolayout اختيار حل يقلل من الخطأ في القيد الآخر (أولوية عالية ولكن غير مطلوب). إن تقليل الخطأ يعني جعل العرض أقرب ما يمكن إلى عرض الحاوية ، بينما لا ينتهك القيد المطلوب أقل من أو يساوي.

نفس الشيء يحدث مع قيود الارتفاع. ونظرًا لأن قيد نسبة العرض إلى الارتفاع مطلوب أيضًا ، فإنه لا يمكن إلا زيادة حجم العرض الفرعي على طول محور واحد (ما لم يحدث للحاوية أن يكون لها نفس نسبة العرض إلى الارتفاع مثل العرض الفرعي).

لذلك ، أولاً ، أقوم بإنشاء قيد نسبة العرض إلى الارتفاع:

ثم أقوم بإنشاء قيود متساوية العرض والارتفاع مع الحاوية:

أحتاج لتحرير هذه القيود لتكون أقل من أو تساوي القيود:

بعد ذلك ، أحتاج إلى إنشاء مجموعة أخرى من قيود العرض والارتفاع متساوية مع الحاوية:

وأحتاج إلى جعل هذه القيود الجديدة أقل من الأولوية المطلوبة:

أخيرًا ، طلبت أن يتم توسيط العرض الفرعي في الحاوية الخاصة به ، لذلك سأقوم بإعداد هذه القيود:

الآن ، للاختبار ، سوف أقوم بتحديد وحدة التحكم في العرض واطلب Xcode لتحديث كل الإطارات. هذا ما أحصل عليه:

وجه الفتاة! وقد توسعت العرض الفرعي لتعبئة الحاوية بالكامل. إذا قمت بتحديده ، يمكنني أن أرى أنه في الواقع الحفاظ على نسبة العرض إلى الارتفاع الخاصة به ، لكنه يقوم بعملية ملء الجانب بدلاً من المظهر المناسب .

المشكلة هي أنه عند وجود قيد أقل من أو يساوي ، فإنه من المهم أن يكون المشهد في كل نهاية القيد ، وأنشأت Xcode القيد المقابل من توقعاتي. يمكنني تحديد كل من القيود اثنين وعكس البنود الأولى والثانية. بدلاً من ذلك ، سأقوم فقط بتحديد العرض الفرعي وتغيير القيود ليكون أكبر من أو يساوي:

Xcode بتحديث التخطيط:

الآن أفعل كل نفس الأشياء إلى عرض الأخضر الداكن في الأسفل. أحتاج إلى التأكد من أن نسبة العرض إلى الارتفاع هي 1: 4 (تم تغيير حجم Xcode بطريقة غريبة نظرًا لعدم وجود قيود). لن أعرض الخطوات مرة أخرى نظرًا لأنها متشابهة. وهنا النتيجة:

الآن يمكنني تشغيله في جهاز محاكاة iPhone 4S ، والذي يحتوي على حجم شاشة مختلف عن IB المستخدم ودوران الاختبار:

ويمكنني اختبار في جهاز محاكاة iPhone 6:

لقد قمت بتحميل لوحة العمل النهائية الخاصة بي إلى هذه الباحثة لراحتك.

أرغب في استخدام ميزة "التدريج التلقائي" لحجم وتخطيط العرض بطريقة تذكرنا بنمط المحتوى الملائم لجانب UIImageView.

لدي عرض فرعي داخل عرض حاوية في Interface Builder. يحتوي العرض الفرعي على بعض نسبة العرض إلى الارتفاع الأساسية التي أود أن أحترمها. حجم عرض الحاوية غير معروف حتى وقت التشغيل.

إذا كانت نسبة العرض إلى الارتفاع في عرض الحاوية أوسع من العرض الفرعي ، فأنا أريد أن يساوي ارتفاع subview ارتفاع العرض الرئيسي.

إذا كانت نسبة العرض إلى الارتفاع في عرض الحاوية أطول من العرض الفرعي ، فأنا أريد عرض العرض الفرعي ليعادل عرض العرض الرئيسي.

في كلتا الحالتين ، أتمنى أن تتم توسعة الوحدة الفرعية أفقيًا وعموديًا داخل عرض الحاوية.

هل هناك طريقة لتحقيق ذلك باستخدام قيود AutoLayout في Xcode 6 أو في الإصدار السابق؟ من الناحية المثالية باستخدام واجهة منشئ ، ولكن إذا لم يكن ربما من الممكن تحديد مثل هذه القيود برمجيا.


ربما هذا هو أقصر جواب ، مع 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);
        }
    }
}];

هذا هو منفذ لإجابة @ rob_mayoff ممتازة لمقاربة مرتكزة على التعليمات البرمجية ، وذلك باستخدام كائنات NSLayoutAnchor ونقلها إلى Xamarin. بالنسبة لي ، NSLayoutAnchor والفصول ذات الصلة برنامج AutoLayout أسهل بكثير:

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 ();
    }
}

وجدت نفسي في حاجة إلى سلوك ملء الجانب بحيث يحافظ UIImageView على نسبة العرض إلى الارتفاع الخاصة به وملء عرض الحاوية بالكامل. ومما يبعث على الارتباك ، أن UIImageView كان يكسر كل من الأفضلية ذات الأفضلية العالية والمتساوية والمتساوية الارتفاع (موصوفة في إجابة روب) وتقديمها بدقة كاملة.

كان الحل ببساطة هو تعيين أولوية مقاومة ضغط محتوى UIImageView أقل من الأولوية لقيود متساوية العرض والمتساوية الارتفاع:


روب ، إجابتك رائعة! أعلم أيضًا أن هذا السؤال يتعلق تحديدًا بهذا عن طريق استخدام التخطيط التلقائي. ومع ذلك ، فقط كمرجع ، أود أن أبين كيف يمكن القيام بذلك في الكود. قمت بإعداد وجهات النظر العلوية والسفلية (الأزرق والوردي) فقط مثل أظهر روب. ثم تقوم بإنشاء 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 s. وأخيرًا ، يمكنك تعيين منفذين على viewcontroller الخاص بك topAspectFitView و bottomAspectFitView وتعيين childView s الخاصة بهم في viewDidLoad :

- (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