मैं एक कस्टम iOS व्यू क्लास कैसे बना सकता हूँ और इसकी कई प्रतियाँ (आईबी में) इंस्टेंटिएट कर सकता हूँ?


114

मैं वर्तमान में एक ऐप बना रहा हूं जिसमें कई टाइमर होंगे, जो मूल रूप से सभी समान हैं।

मैं एक कस्टम वर्ग बनाना चाहता हूं जो सभी कोड का उपयोग टाइमर के साथ-साथ लेआउट / एनिमेशन के लिए भी करता है, इसलिए मेरे पास 5 समान टाइमर हो सकते हैं जो एक दूसरे से स्वतंत्र रूप से संचालित होते हैं।

मैंने IB (xcode 4.2) का उपयोग करते हुए लेआउट बनाया और टाइमर के लिए सभी कोड वर्तमान में केवल दृश्य-केंद्र नियंत्रित वर्ग में हैं।

मुझे अपने मस्तिष्क को लपेटने में कठिनाई हो रही है कि कैसे एक कस्टम वर्ग में सब कुछ एनकैप्सुलेट किया जाए और फिर इसे व्यू कंट्रौलर में जोड़ा जाए, किसी भी मदद की बहुत सराहना की जाएगी।


किसी को भी यहाँ जाने के लिए ... यह अब आईओएस में आने वाले कंटेनर विचारों से पांच साल है ! कंटेनर दृश्य मूल, केंद्रीय विचार हैं कि आप iOS में अब सब कुछ कैसे करते हैं। वे उपयोग करने के लिए अविश्वसनीय रूप से सरल हैं, और कंटेनर के
नज़दीक

2
@JoeBlow को छोड़कर आप UITableViewCellया में कंटेनर दृश्य का उपयोग नहीं कर सकते UICollectionViewCell। मेरे मामले में मुझे एक छोटे लेकिन काफी जटिल दृष्टिकोण की आवश्यकता है जिसे मैं नियंत्रकों और संग्रह के विचारों में कई बार उपयोग कर सकता हूं। और डिजाइनर इसे फिर से काम करते हैं, इसलिए मैं चाहता हूं कि एक एकल स्थान लेआउट को परिभाषित किया जाए। इसलिए, एक निब।
इचेलन

जवाबों:


142

वैचारिक रूप से जवाब देने के लिए, आपके टाइमर के UIViewबजाय की एक उपवर्ग होना चाहिए NSObject

IB में अपने टाइमर की एक आवृत्ति को तुरंत करने के लिए UIViewइसे अपने व्यू कंट्रोलर के दृश्य पर ड्रॉप आउट करें, और इसे अपने टाइम के क्लास के नाम पर सेट करें।

यहां छवि विवरण दर्ज करें

स्मरण में रखना #importअपने व्यू कंट्रोलर में अपने टाइमर क्लास ।

संपादित करें: आईबी डिजाइन के लिए (कोड तात्कालिकता के लिए संशोधन इतिहास देखें)

मैं स्टोरीबोर्ड के साथ बिल्कुल परिचित नहीं हूं, लेकिन मुझे पता है कि आप आईबी में अपने इंटरफ़ेस का निर्माण एक .xibफाइल का उपयोग करके कर सकते हैं जो स्टोरीबोर्ड संस्करण का उपयोग करने के लिए लगभग समान है; आपको अपने मौजूदा इंटरफ़ेस से .xibफ़ाइल तक संपूर्ण रूप में अपने विचारों को कॉपी और पेस्ट करने में सक्षम होना चाहिए ।

इसका परीक्षण करने के लिए मैंने .xib"MyCustomTimerView.xib" नामक एक नया खाली बनाया । फिर मैंने एक दृश्य जोड़ा, और उसमें एक लेबल और दो बटन जोड़े। इस तरह:

यहां छवि विवरण दर्ज करें

मैंने UIView"MyCustomTimer" नाम से एक नया उद्देश्य-सी वर्ग उपवर्ग बनाया । अपने में .xibमैंने MyCustomTimer होने के लिए अपनी फ़ाइल का स्वामी वर्ग निर्धारित किया है । अब मैं किसी भी अन्य दृश्य / नियंत्रक की तरह कार्यों और दुकानों को जोड़ने के लिए स्वतंत्र हूं। परिणामी .hफ़ाइल इस तरह दिखती है:

@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end

कूदने के लिए केवल एकमात्र बाधा .xibमेरे UIViewउपवर्ग पर मिल रही है । .xibआवश्यक रूप से सेटअप में नाटकीय रूप से कटौती का उपयोग करना । और जब से आप स्टोरीबोर्ड का उपयोग कर रहे हैं, हमें पता -(id)initWithCoder:है कि टाइमर को लोड करने के लिए केवल एक ही इनिशलाइज़र है जिसे कहा जाएगा। तो यहाँ क्या कार्यान्वयन फ़ाइल की तरह दिखता है:

#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self addSubview:
         [[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" 
                                        owner:self 
                                      options:nil] objectAtIndex:0]];
    }
    return self;
}
- (IBAction)startButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor redColor];
}
@end

नाम की विधि loadNibNamed:owner:options: बिल्कुल वैसा ही करती है जैसा उसे लगता है। यह निब को लोड करता है और "फाइल का मालिक" संपत्ति को स्वयं सेट करता है। हम सरणी में पहली वस्तु को निकालते हैं और यह निब का मूल दृश्य है। हम स्क्रीन पर एक सबव्यू और वोइला के रूप में दृश्य जोड़ते हैं।

जाहिर है कि यह केवल बटन को धकेलने पर लेबल की पृष्ठभूमि का रंग बदलता है, लेकिन यह उदाहरण आपको अपने रास्ते पर अच्छी तरह से मिलना चाहिए।

टिप्पणी के आधार पर नोट्स:

यह ध्यान देने योग्य है कि यदि आपको अनंत पुनरावृत्ति की समस्या हो रही है, तो आप शायद इस समाधान की सूक्ष्म चाल से चूक गए हैं। यह वह नहीं कर रहा है जो आपको लगता है कि यह कर रहा है। स्टोरीबोर्ड में डाला गया दृश्य नहीं देखा जाता है, लेकिन इसके बजाय एक अन्य दृश्य को एक सबव्यू के रूप में लोड करता है। यह दृश्य लोड करता है वह दृश्य है जो nib में परिभाषित किया गया है। निब में "फ़ाइल का मालिक" वह अनदेखी दृश्य है। ठंडा हिस्सा यह है कि यह अनदेखी दृश्य अभी भी एक ऑब्जेक्टिव-सी क्लास है, जिसे देखने के लिए एक तरह के व्यू कंट्रोलर के रूप में इस्तेमाल किया जा सकता है। उदाहरण के लिए IBActionतरीकों मेंMyCustomTimer कक्षा कुछ ऐसी होती हैं जिनसे आप किसी दृश्य नियंत्रक की अपेक्षा अधिक हो सकते हैं।

एक साइड नोट के रूप में, कुछ तर्क दे सकते हैं कि यह टूट गया MVCऔर मैं कुछ हद तक सहमत हूं। मेरे दृष्टिकोण से यह एक प्रथा से अधिक निकटता से संबंधित है UITableViewCell, जिसे कभी-कभी भाग नियंत्रक भी होना पड़ता है।

यह भी ध्यान देने योग्य है कि यह उत्तर एक बहुत विशिष्ट समाधान प्रदान करना था; एक स्टोरीबोर्ड पर एक ही दृश्य बनाने के लिए एक ही बार में एक ही बार में एक ही बार बनाया जा सकता है । उदाहरण के लिए, आप आसानी से एक समय में एक iPad स्क्रीन पर इन टाइमर के छह की कल्पना कर सकते हैं। यदि आपको केवल एक दृश्य नियंत्रक के लिए एक दृश्य निर्दिष्ट करने की आवश्यकता है जिसे आपके आवेदन में कई बार उपयोग किया जाना है तो इस प्रश्न के लिए ज्येवार्ड द्वारा प्रदान किया गया समाधान लगभग निश्चित रूप से आपके लिए एक बेहतर समाधान है।


यह वास्तव में एक अलग प्रश्न की तरह लगता है। लेकिन मुझे लगता है कि इसका जवाब जल्दी से दिया जा सकता है, इसलिए यहां जाता है। हर बार जब आप एक नया नोटिफिकेशन बनाते हैं, तो यह एक नया नोटिफिकेशन होता है, लेकिन अगर आपको उनकी जरूरत है तो एक ही नाम के साथ उन्हें अलग करने के लिए कुछ जोड़ना चाहते हैं तो userInfoनोटिफिकेशन पर प्रॉपर्टी का इस्तेमाल करें@property(nonatomic,copy) NSDictionary *userInfo;
NJones

28
मुझे initWithCoder से एक अनंत पुनरावर्तन मिलता है। कोई विचार?
गायब हो गया

6
एक पुराने धागे को पुनर्जीवित करने के लिए क्षमा करें, सुनिश्चित करें कि File's Ownerआपकी कक्षा का नाम मेरी अनंत समस्या का समाधान कर दिया गया है।
थीनातुफ

20
अनंत पुनरावृत्ति का एक अन्य कारण आपके कस्टम वर्ग के लिए xib में मूल दृश्य सेट करना है। आप ऐसा नहीं करना चाहते हैं, इसे "UIView"
XY

11
इस दृष्टिकोण के बारे में मेरा ध्यान आकर्षित करने वाली सिर्फ एक चीज है ... क्या यह किसी को परेशान नहीं करता है कि 2 दृश्य अब एक ही कक्षा में हैं? मूल फ़ाइल .h / .m को UIView से विरासत में मिला है पहला एक है और [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]]; दूसरे UIView को जोड़कर ... यह थोड़े मुझे किसी और के लिए अजीब लगता है उसी तरह लगता है? यदि ऐसा है तो यह कुछ सेब डिज़ाइन द्वारा बनाया गया है? या ऐसा करने से कोई समाधान मिल जाता है?
SH

137

स्विफ्ट उदाहरण

Xcode 10 और Swift 4 के लिए अपडेट किया गया

यहाँ एक बुनियादी चलना है। मैंने मूल रूप से इस Youtube वीडियो श्रृंखला को देखने से बहुत कुछ सीखा है । बाद में मैंने इस लेख के आधार पर अपना उत्तर अपडेट किया ।

कस्टम दृश्य फ़ाइलें जोड़ें

निम्नलिखित दो फाइलें आपका कस्टम दृश्य बनाएंगी:

  • .xib फ़ाइल में लेआउट सम्‍मिलित है
  • .swift फ़ाइल को UIViewउपवर्ग के रूप में

उन्हें जोड़ने के लिए विवरण नीचे हैं।

Xib फ़ाइल

अपने प्रोजेक्ट में एक .xib फ़ाइल जोड़ें (फ़ाइल> नई> फ़ाइल ...> उपयोगकर्ता इंटरफ़ेस> दृश्य) । मुझे मेरा फोन आ रहा है ReusableCustomView.xib

वह लेआउट बनाएं जिसे आप चाहते हैं कि आपका कस्टम दृश्य हो। एक उदाहरण के रूप में, मैं ए UILabelऔर ए के साथ एक लेआउट बनाऊंगा UIButton। यह ऑटो लेआउट का उपयोग करने के लिए एक अच्छा विचार है ताकि चीजें स्वचालित रूप से आकार ले लें, इससे कोई फर्क नहीं पड़ता कि आप इसे बाद में किस आकार में सेट करते हैं। (मैं इस्तेमाल किया मुक्त आकार इतना है कि मैं नकली मैट्रिक्स समायोजित कर सकते हैं गुण निरीक्षक में xib आकार के लिए, लेकिन यह आवश्यक नहीं है।)

यहां छवि विवरण दर्ज करें

स्विफ्ट फ़ाइल

अपने प्रोजेक्ट में एक .swift फ़ाइल जोड़ें (फ़ाइल> नई> फ़ाइल ...> स्रोत> स्विफ़्ट फ़ाइल) । यह एक उपवर्ग है UIViewऔर मुझे मेरा कहा जाता है ReusableCustomView.swift

import UIKit
class ResuableCustomView: UIView {

}

स्विफ्ट फ़ाइल को स्वामी बनाएं

अपनी .xib फ़ाइल पर वापस जाएं और दस्तावेज़ रूपरेखा में "फ़ाइल का स्वामी" पर क्लिक करें। पहचानकर्ता निरीक्षक में अपने .swift फ़ाइल का नाम कस्टम वर्ग के नाम के रूप में लिखें

यहां छवि विवरण दर्ज करें

कस्टम दृश्य कोड जोड़ें

ReusableCustomView.swiftफ़ाइल की सामग्री को निम्न कोड से बदलें :

import UIKit

@IBDesignable
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView:UIView?

    @IBOutlet weak var label: UILabel!

    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    func commonInit() {
        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

अपने .xib फ़ाइल के नाम के लिए वर्तनी सही होना सुनिश्चित करें।

आउटलेट और कार्रवाई को हुक करें

एक्सेल लेआउट में लेबल और बटन से स्विफ्ट कस्टम व्यू कोड पर खींचकर नियंत्रण और आउटलेट को हुक करें।

आप कस्टम दृश्य का उपयोग करें

अब आपका कस्टम दृश्य समाप्त हो गया है। आपको बस इतना करना है UIViewकि आप इसे अपने मुख्य स्टोरीबोर्ड में जहां भी चाहें डाल सकते हैं। ReusableCustomViewपहचान निरीक्षक में दृश्य का वर्ग नाम सेट करें ।

यहां छवि विवरण दर्ज करें


31
महत्वपूर्ण Xib दृश्य वर्ग को ResuableCustomView के रूप में सेट नहीं करना है, लेकिन इस Xib का स्वामी है। अन्यथा आप में त्रुटि होगी।
RealNmae

5
हाँ, अच्छा अनुस्मारक। मुझे नहीं पता कि मैंने कक्षा के नाम के लिए Xib के दृश्य (बल्कि फ़ाइल स्वामी) को सेट करने के कारण होने वाली अनंत पुनरावृत्ति समस्याओं को हल करने की कोशिश में कितना समय बर्बाद किया है।
सूरगाछ

2
@TomCalmon, मैंने इस प्रोजेक्ट को Xcode 8 में स्विफ्ट 3 के साथ रिडीम किया और ऑटो लेआउट मेरे लिए ठीक काम कर रहा था।
सुरगाछ

6
अगर किसी और को IB में व्यू रेंडरिंग में कोई समस्या है, तो मैंने पाया कि ट्रिक Bundle.mainको बदलना Bundle(for: ...)। जाहिर है, Bundle.mainडिजाइन समय पर उपलब्ध नहीं है।
crizzis

4
@IBDesignable के साथ काम नहीं करने का कारण यह है कि आपने लागू नहीं किया init(frame:)। एक बार जब आप इसे जोड़ते हैं (और किसी commonInit()समारोह में सभी आरंभीकरण कोड खींचते हैं ) यह आईबी में ठीक काम करता है और दिखाता है। अधिक जानकारी यहां देखें: stackoverflow.com/questions/31265906/…
दिमित्रिस

39

व्यू कंट्रोलर्स के लिए उत्तर, न कि विचार :

स्टोरीबोर्ड से xib को लोड करने का एक आसान तरीका है। कहते हैं कि आपका कंट्रोलर MyClassController प्रकार का है जो इनहेरिट करता है UIViewController

आप UIViewControllerअपने स्टोरीबोर्ड में एक उपयोग आईबी जोड़ें ; वर्ग प्रकार को परिवर्तित करें MyClassController। वह दृश्य हटाएं जो स्टोरीबोर्ड में स्वचालित रूप से जोड़ा गया था।

सुनिश्चित करें कि जिस XIB को आप चाहते हैं उसे MyClassController.xib कहा जाता है।

जब स्टोरीबोर्ड लोडिंग के दौरान क्लास को इंस्टेंट किया जाएगा, तो xib अपने आप लोड हो जाएगा। इसका कारण डिफ़ॉल्ट कार्यान्वयन है UIViewControllerजिसके कारण XIB को वर्ग नाम से पुकारा जाता है।


1
मैं हर समय कस्टम दृश्य बढ़ाता हूं, लेकिन इसे स्टोरीबोर्ड पर लोड करने की इस छोटी सी चाल को कभी नहीं जानता था। बहुत सराहना की!
मृदांत

38
प्रश्न विचारों के बारे में है, न कि दृश्य नियंत्रकों के बारे में।
रिकार्डो

स्पष्टीकरण के लिए जोड़ा गया शीर्षक, "व्यू कंट्रोलर्स के लिए उत्तर, विचार नहीं:"
nacho4d

1
IOS 9.1 में काम नहीं करता है। यदि मैं स्वतः-निर्मित (डिफ़ॉल्ट) दृश्य को हटाता हूं, तो यह वीसी के दृश्यडीडलड से लौटने के बाद क्रैश हो जाता है। मुझे नहीं लगता कि XIB में दृश्य डिफ़ॉल्ट दृश्य के स्थान पर जुड़ा हुआ है।
जेफ

1
मुझे जो अपवाद मिल रहा है, वह है 'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''। व्हेको निबनामे पर ध्यान दें। मैं निब में सही वर्ग नाम का उपयोग कर रहा हूं।
जेफ

16

यह वास्तव में एक उत्तर नहीं है, लेकिन मुझे लगता है कि इस दृष्टिकोण को साझा करना सहायक है।

उद्देश्य सी

  1. अपनी परियोजना के लिए CustomViewWithXib.h और CustomViewWithXib.m आयात करें
  2. एक ही नाम (.h / .m / .xib) के साथ कस्टम दृश्य फ़ाइलें बनाएँ
  3. CustomViewWithXib से अपनी कस्टम क्लास इनहेरिट करें

तीव्र

  1. अपनी परियोजना के लिए CustomViewWithXib.swift आयात करें
  2. एक ही नाम (.swift और .xib) के साथ कस्टम दृश्य फ़ाइलें बनाएँ
  3. CustomViewWithXib से अपनी कस्टम क्लास इनहेरिट करें

वैकल्पिक:

  1. अपनी xib फ़ाइल पर जाएं, अपने कस्टम वर्ग के नाम के साथ स्वामी को सेट करें यदि आपको कुछ तत्वों को जोड़ने की आवश्यकता है (अधिक विवरण के लिए भाग देखें स्विफ्ट फ़ाइल का मालिक @Suragch का उत्तर दें)

यह सब है, अब आप अपने स्टोरीबोर्ड में अपने कस्टम दृश्य जोड़ सकते हैं और यह दिखाया जाएगा :)

CustomViewWithXib.h :

 #import <UIKit/UIKit.h>

/**
 *  All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
 MyCustomView.h
 MyCustomView.m
 MyCustomView.xib
 */

// This allows seeing how your custom views will appear without building and running your app after each change.
IB_DESIGNABLE
@interface CustomViewWithXib : UIView

@end

CustomViewWithXib.m :

#import "CustomViewWithXib.h"

@implementation CustomViewWithXib

#pragma mark - init methods

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

#pragma mark - setup view

- (UIView *)loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];

    //  An exception will be thrown if the xib file with this class name not found,
    UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class])  owner:self options:nil] firstObject];
    return view;
}

- (void)commonSetup {
    UIView *nibView = [self loadViewFromNib];
    nibView.frame = self.bounds;
    // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
    nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    // Adding nibView on the top of our view
    [self addSubview:nibView];
}

@end

CustomViewWithXib.swift :

import UIKit

@IBDesignable
class CustomViewWithXib: UIView {

    // MARK: init methods
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        commonSetup()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        commonSetup()
    }

    // MARK: setup view 

    private func loadViewFromNib() -> UIView {
        let viewBundle = NSBundle(forClass: self.dynamicType)
        //  An exception will be thrown if the xib file with this class name not found,
        let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
        return view as! UIView
    }

    private func commonSetup() {
        let nibView = loadViewFromNib()
        nibView.frame = bounds
        // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
        nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        // Adding nibView on the top of our view
        addSubview(nibView)
    }
}

आप यहां कुछ उदाहरण पा सकते हैं ।

उम्मीद है की वो मदद करदे।


तो हम कैसे देखें uicontrols के लिए iboutlet जोड़ सकते हैं
अमृत ​​गुस्सा

1
अरे @ArrAngry, उन विचारों को जोड़ने के लिए जिन्हें आपको 4 चरण में करना चाहिए: "फ़ाइल के स्वामी" में अपनी xib फ़ाइल पर जाएं, अपनी कक्षा सेट करें, और स्पर्श "ctrl" पर टैप करके अपने विचारों को इंटरफ़ेस में खींचें और छोड़ें, मैं अपडेट करता हूं इस उदाहरण के साथ कोड स्रोत, संकोच न करें यदि आपके पास कोई प्रश्न है, तो उम्मीद है कि मदद करता है
HamzaGhazouani

आपके रिप्ले के लिए बहुत बहुत धन्यवाद, मैं पहले से ही ऐसा करता हूं लेकिन मुझे लगता है कि मेरी समस्या इस भाग में नहीं है। मेरी समस्या यह है कि मैं एक UIDatePIcker जोड़ रहा हूं और ViewController से इस नियंत्रण के लिए एक लक्ष्य कार्रवाई जोड़ने की कोशिश करता हूं और uiControleer ईवेंट को बदल दिया जाता है, लेकिन विधि को कभी भी आग नहीं कहा जाता है
अमृत ​​गुस्से में

1
@ArrAngry मैं पिकर दृश्य जोड़कर उदाहरण को अपडेट करता हूं, हो सकता है कि यह आपकी मदद करेगा :) github.com/
HamzaGhazouani

1
धन्यवाद मैं लंबे समय से इसे ढूंढ रहा था।
MH175
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.