আমার অ্যাপ্লিকেশনটির অ্যাপস্টোরে নতুন সংস্করণ রয়েছে কিনা তা পরীক্ষা করুন


111

আমি ব্যবহারকারী হিসাবে থাকা অবস্থায় আমার অ্যাপ্লিকেশনটির জন্য নতুন আপডেট আছে কিনা তা আমি নিজেই যাচাই করতে চাই এবং তাকে নতুন সংস্করণটি ডাউনলোড করতে অনুরোধ করব। আমি কি অ্যাপ স্টোরটিতে আমার অ্যাপ্লিকেশনটির সংস্করণটি পরীক্ষামূলকভাবে পরীক্ষা করে এটি করতে পারি?


6
আপনি কোনও ওয়েব-সার্ভারে একটি এলোমেলো পৃষ্ঠা রাখতে পারেন যা সর্বশেষতম সংস্করণের স্ট্রিং প্রতিনিধিত্ব করে। এটি ডাউনলোড করুন এবং অ্যাপ্লিকেশন শুরু হওয়ার সাথে তুলনা করুন এবং ব্যবহারকারীকে অবহিত করুন। (দ্রুত এবং সহজ উপায়)
লুহোপলি

1
ধন্যবাদ, তবে আমি এমন একধরণের এপিআইয়ের মতো আরও ভাল সমাধানের জন্য আশা করছিলাম যা দিয়ে আমি অ্যাপ স্টোরের কার্যকারিতা কল করতে পারি, যেমন আমার অ্যাপ্লিকেশন নম্বরটি অনুসন্ধান করতে এবং সংস্করণ ডেটা পেতে। কেবল এই উদ্দেশ্যে একটি ওয়েবসারভার বজায় রাখতে সময় সাশ্রয় করে তবে যাইহোক পয়েন্টারের জন্য ধন্যবাদ!
ব্যবহারকারী542584

আমি প্রথম মন্তব্য হিসাবে একই জিনিস। আমি একটি এন্ট্রি সহ একটি প্লিস্ট লিখেছি: একটি NSNumberসংস্করণ নম্বর। তারপরে আমি এটি আমার ওয়েবসাইটে আপলোড করেছি। আমি আমার অ্যাপ্লিকেশন সমর্থন এবং অ্যাপ্লিকেশন ওয়েবপৃষ্ঠাগুলির জন্য একই ওয়েবসাইটটি ব্যবহার করি, তারপরে viewDidLoadআমি সেখানে সংস্করণ নম্বরটির জন্য ওয়েবসাইটটি চেক করি এবং আমি আমার অ্যাপ্লিকেশনে বর্তমান সংস্করণটি পরীক্ষা করি। তারপরে আমার একটি প্রিমেড রয়েছে alertViewযা স্বয়ংক্রিয়ভাবে অ্যাপ্লিকেশনটি আপডেট করতে অনুরোধ করে। আপনি চাইলে আমি কোড সরবরাহ করতে পারি।
অ্যান্ড্রু

ধন্যবাদ, আমি অনুমান করি আমারও এটি চেষ্টা করা উচিত ..
user542584

উত্তর:


88

এখানে একটি সাধারণ কোড স্নিপেট যা আপনাকে জানায় যে বর্তমান সংস্করণটি আলাদা কিনা

-(BOOL) needsUpdate{
    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString* appID = infoDictionary[@"CFBundleIdentifier"];
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
    NSData* data = [NSData dataWithContentsOfURL:url];
    NSDictionary* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

    if ([lookup[@"resultCount"] integerValue] == 1){
        NSString* appStoreVersion = lookup[@"results"][0][@"version"];
        NSString* currentVersion = infoDictionary[@"CFBundleShortVersionString"];
        if (![appStoreVersion isEqualToString:currentVersion]){
            NSLog(@"Need to update [%@ != %@]", appStoreVersion, currentVersion);
            return YES;
        }
    }
    return NO;
}

দ্রষ্টব্য: আপনি আইটিউনে নতুন সংস্করণটি প্রবেশ করার পরে, আপনি যে অ্যাপটি প্রকাশ করছেন তার সংস্করণটির সাথে এটি মেলে তা নিশ্চিত করুন। যদি না হয় তবে উপরের কোডটি সর্বদা YES ফিরিয়ে দেবে যদি ব্যবহারকারী আপডেট না করে।


4
সুপার সমাধান আমি খুঁজে পেয়েছি +1
সঞ্জয় চাঙ্গানি

1
@ মোবিনাআফজাল, আমি মনে করি আপনি প্রশ্ন এবং সমাধান বুঝতে পেরেছেন। উপরের সমাধানটি স্টোরের সংস্করণটির সাথে বর্তমান সংস্করণটির তুলনা করে। যদি তারা না মেলে তবে এটি হ্যাঁ পুনরায় সংযুক্ত করে, অন্যথায় এটি কোনও ফেরত দেয় না। উপরের পদ্ধতিটি অ্যাপ স্টোরের ইতিহাস কোনও বিষয় নয় যদি বর্তমান সংস্করণটি আলাদা হয় তবে অ্যাপ স্টোর সংস্করণটি হ্যাঁ ফিরিয়ে দেবে। একবার ব্যবহারকারী আপডেট হয়ে যায় ... বর্তমান সংস্করণটি অ্যাপ স্টোর সংস্করণের সমান। উপরের পদ্ধতিটি ব্যবহারকারীর সংস্করণ ১.০ এবং অ্যাপ স্টোর সংস্করণ ১.২ হলে সর্বদা হ্যাঁ ফিরিয়ে নেওয়া উচিত।
ডাটিন

1
@ মোবীনআফজাল আমার মনে হয় আপনি যা দেখছেন তা পেয়েছি। কোডটিতে আপনার সংস্করণটি 1.7, তবে আইটিউনসে আপনি সংস্করণটি 1.6 হিসাবে আপলোড করেছেন যাতে আপনার ব্যবহারকারীরা জানেন না যে আপনি কোনও সংস্করণ এড়িয়ে গেছেন। এটাই কি? যদি তা হয় তবে ... আপনার অ্যাপ্লিকেশন সংস্করণ নম্বরটি পরিবেশন করতে এবং সেই শেষ পয়েন্টটিতে অ্যাক্সেস করার জন্য আপনার কোডটি সংশোধন করার জন্য আপনার যা দরকার তা হল একটি সার্ভার (ড্রপবক্স করবে)। আপনি যদি এটি দেখতে পাচ্ছেন তা হলে আমাকে জানান এবং আমি পোস্টে সতর্কতার নোট যুক্ত করব।
ডাটিন্ক

1
@ মোবিউন আফজাল আপনার মন্তব্যটি বিভ্রান্তিকর। যদি ব্যবহারকারীর ডিভাইসের সংস্করণটি অ্যাপস্টোরের সংস্করণ থেকে যে কোনও দ্বারা আলাদা করা হয় তবে কোডটি প্রত্যাশা অনুযায়ী হ্যাঁ প্রত্যাবর্তন করবে। এমনকি আপনি যদি সংস্করণ ১.১১১ অনুসারে সংস্করণ প্রকাশ করেন তবে এটি এখনও পুরোপুরি কার্যকর হবে।
ডাটিন

1
নিম্নোক্তভাবে অ্যাপস্টোর সংস্করণটি বর্তমান সংস্করণের চেয়ে বড় হলে আমাদের আপডেটটি দেখা উচিত। যদি ([অ্যাপস্টোর ভার্সন তুলনা করুন: কারেন্ট ভার্সন বিকল্পগুলি: এনএসনিউমারিকস অনুসন্ধান] == এনএসআরডডিসেন্ডিং) S এনএসএলগ (@ "\ n update n আপডেট করা দরকার App ।
নীতেশ বোরাড

52

সুইফট 3 সংস্করণ:

func isUpdateAvailable() throws -> Bool {
    guard let info = Bundle.main.infoDictionary,
        let currentVersion = info["CFBundleShortVersionString"] as? String,
        let identifier = info["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
        throw VersionError.invalidBundleInfo
    }
    let data = try Data(contentsOf: url)
    guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else {
        throw VersionError.invalidResponse
    }
    if let result = (json["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String {
        return version != currentVersion
    }
    throw VersionError.invalidResponse
}

আমি মনে করি মিথ্যা প্রত্যাবর্তনের পরিবর্তে ত্রুটি নিক্ষেপ করা ভাল, এক্ষেত্রে আমি একটি সংস্করণআরর তৈরি করেছি তবে এটি আপনি সংজ্ঞায়িত বা এনএসইরর অন্য কিছু হতে পারেন

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

এই ফাংশনটিকে অন্য থ্রেড থেকে কল করতেও বিবেচনা করুন, সংযোগটি ধীর হয়ে থাকলে এটি বর্তমান থ্রেডটি ব্লক করতে পারে।

DispatchQueue.global().async {
    do {
        let update = try self.isUpdateAvailable()
        DispatchQueue.main.async {
            // show alert
        }
    } catch {
        print(error)
    }
}

হালনাগাদ

ইউআরএলসেশন ব্যবহার:

Data(contentsOf: url)কোনও থ্রেড ব্যবহার এবং অবরোধ করার পরিবর্তে আমরা ব্যবহার করতে পারি URLSession:

func isUpdateAvailable(completion: @escaping (Bool?, Error?) -> Void) throws -> URLSessionDataTask {
    guard let info = Bundle.main.infoDictionary,
        let currentVersion = info["CFBundleShortVersionString"] as? String,
        let identifier = info["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
            throw VersionError.invalidBundleInfo
    }
    Log.debug(currentVersion)
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        do {
            if let error = error { throw error }
            guard let data = data else { throw VersionError.invalidResponse }
            let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
            guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String else {
                throw VersionError.invalidResponse
            }
            completion(version != currentVersion, nil)
        } catch {
            completion(nil, error)
        }
    }
    task.resume()
    return task
}

উদাহরণ:

_ = try? isUpdateAvailable { (update, error) in
    if let error = error {
        print(error)
    } else if let update = update {
        print(update)
    }
}

1
এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी 10

4
আমি অসম্মতি জানাই, DispatchQueue.global()আপনাকে একটি পটভূমি সারি দেয়, তথ্যটি সেই সারিটিতে লোড করা হয় এবং কেবল ডেটা লোড হওয়ার পরে মূল কাতারে ফিরে যায়।
জুয়ানজো

উপস। কোনওভাবেই আমি সেই দ্বিতীয় কোড স্নিপেট উপেক্ষা করেছি। দুঃখের বিষয়, আপনার উত্তরটি আবার সম্পাদনা না করা অবধি আমি ডাউনভোটটি সরিয়ে ফেলতে পারছি না :-( বিটিডাব্লু - প্রদত্ত ডেটাউইথ কনটেন্টস অফ ইউআরএল: আসলে এনএসআরএল সংযোগের সিঙ্ক্রোনাস কলগুলি দিয়ে যায়, যার ফলস্বরূপ কেবল একটি অ্যাসিঙ্ক থ্রেড এবং ব্লক শুরু হয়, এটি সম্ভবত কম ওভারহেড হবে । শুধু অ্যাসিঙ্ক্রোনাস NSURLSession কল ব্যবহার করতে তারা এমনকি আপনি মুখ্য থ্রেডে কল ব্যাক একবার আপনার সম্পন্ন হয়ে চাই।
uliwitness

@ জুয়ানজো ,,,, সুইফট ৩.০.১ এর জন্য কাজ করছেন না, দয়া করে আপনি সুইফটের জন্য আপডেট আপলোড করতে পারবেন ???
কিরণ যধব

2
নোট আপনি শুধুমাত্র একটি নির্দিষ্ট দোকান তালিকাভুক্ত করা হয় আমি দেখেছি যে আপনি URL করার জন্য একটি কান্ট্রি কোড যোগ করতে হবে - যেমন গিগাবাইট itunes.apple.com/(countryCode)/... )
রায়ান Heitner

13

তার লিঙ্কটির জন্য স্টিভ মোসরকে ধন্যবাদ, আমার কোডটি এখানে:

NSString *appInfoUrl = @"http://itunes.apple.com/en/lookup?bundleId=XXXXXXXXX";

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:appInfoUrl]];
[request setHTTPMethod:@"GET"];

NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection  sendSynchronousRequest:request returningResponse: &response error: &error];
NSString *output = [NSString stringWithCString:[data bytes] length:[data length]];

NSError *e = nil;
NSData *jsonData = [output dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error: &e];

NSString *version = [[[jsonDict objectForKey:@"results"] objectAtIndex:0] objectForKey:@"version"];

1
খুব ভাল এবং সঠিক সমাধান, ইউআরএল সম্পর্কিত সামান্য আপডেট হ'ল itunes.apple.com/en/lookup?bundleId=xxxxxxxxxx
এসজে

ধন্যবাদ, আপনার মন্তব্য প্রয়োগ হয়েছে
রোজবেহ জবিহোল্লাহি

4
আসলে এটি /en/সাবপাথ নিয়ে আমার পক্ষে কাজ করে নি। এটি অপসারণের পরে, এটি কাজ করেছিল
9:58 এ গ্যাসপার্ফ

এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी

1
আমি / bn / সঙ্গে ব্যবহার করার জন্য ছিল itunes.apple.com/lookup?bundleId=xxxxxxx , @gasparuff ধন্যবাদ
ফার্নান্দো পেরেজ

13

যেহেতু আমি একই সমস্যার মুখোমুখি হয়েছিলাম, তাই মারিও হেন্ড্রিক্সের দেওয়া উত্তরটি পেয়েছি । আমি যখন আমার প্রকল্পে তার কোডটি অ্যাপ্লিকেশন দেওয়ার চেষ্টা করেছি তখন এক্সকোড কাস্টিং সমস্যা সম্পর্কে অভিযোগ করেছিলেন যে "MDLMaterialProperty এর কোনও সাবস্ক্রিপ্ট সদস্য নেই" has তার কোডটি এই এমডিএলমাটিরিয়ালকে সেট করার চেষ্টা করছিল ... ধ্রুব "ধরণের" লুকের রেজাল্ট "এর ধরণ হিসাবে প্রতিবারের জন্য" ইন্টার "কে কাস্টিং ব্যর্থ করে তোলে। আমার সমাধানটি ছিল এনএসডিকোরিয়রে আমার পরিবর্তনশীলটির জন্য এক প্রকারের টীকা সরবরাহ করা যে ধরণের মান প্রয়োজন তা পরিষ্কার হওয়ারএটির সাথে আমি আমার প্রয়োজনীয় "সংস্করণ" মানটি অ্যাক্সেস করতে পারি।

ওবস : এই ইউআরবান্ডলিডের জন্য , আপনি আপনার এক্সকোড প্রকল্প থেকে পেতে পারেন .... " লক্ষ্যগুলি> সাধারণ> পরিচয়> বান্ডেল আইডেন্টিফায়ার "

সুতরাং কিছু সরলকরণের সাথে আমার কোডটি এখানে রয়েছে:

  func appUpdateAvailable() -> Bool
{
    let storeInfoURL: String = "http://itunes.apple.com/lookup?bundleId=YOURBUNDLEID"
    var upgradeAvailable = false
    // Get the main bundle of the app so that we can determine the app's version number
    let bundle = NSBundle.mainBundle()
    if let infoDictionary = bundle.infoDictionary {
        // The URL for this app on the iTunes store uses the Apple ID for the  This never changes, so it is a constant
        let urlOnAppStore = NSURL(string: storeInfoURL)
        if let dataInJSON = NSData(contentsOfURL: urlOnAppStore!) {
            // Try to deserialize the JSON that we got
            if let dict: NSDictionary = try? NSJSONSerialization.JSONObjectWithData(dataInJSON, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject] {
                if let results:NSArray = dict["results"] as? NSArray {
                    if let version = results[0].valueForKey("version") as? String {
                        // Get the version number of the current version installed on device
                        if let currentVersion = infoDictionary["CFBundleShortVersionString"] as? String {
                            // Check if they are the same. If not, an upgrade is available.
                            print("\(version)")
                            if version != currentVersion {
                                upgradeAvailable = true
                            }
                        }
                    }
                }
            }
        }
    }
    return upgradeAvailable
}

এই কোডের উন্নতির জন্য সমস্ত পরামর্শ স্বাগত!


এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी

@ ইয়াগো জারদো দয়া করে তুলনা ফাংশনটি ব্যবহার করুন অন্যথায় যখন ব্যবহারকারী আপলোড করে থাকে অ্যাপ্লিকেশন পরীক্ষিত সময় প্রদর্শনের আপডেট সতর্কতা বা অ্যাপল আপনার অ্যাপ্লিকেশনটিকে প্রত্যাখ্যান করে
জিগার

আরে @ জিগার, পরামর্শের জন্য ধন্যবাদ। আমি বর্তমানে আমার অ্যাপ্লিকেশনটিতে এই পদ্ধতিটি আর ব্যবহার করছি না কারণ এখন আমরা আমাদের সার্ভারের সমস্ত কিছু সংস্করণ করছি। যাইহোক, আপনি কি বলেছিলেন আরও ভাল ব্যাখ্যা করতে পারেন? আমি বুঝতে পারি নি এবং এটি জানার জন্য এটি সত্যই ভাল লাগে। আগাম ধন্যবাদ.
ইয়াগো জারদো

টিপটির জন্য আপনাকে ধন্যবাদ জানুন @ এই সাক্ষাতকারটি, এটি সত্যই আমাকে অ্যাসিঙ্ক্রোনাস এবং সিঙ্ক্রোনাস অনুরোধগুলি সম্পর্কে জানতে সাধারণভাবে আমার কোডটি উন্নত করতে সহায়তা করেছে helped
ইয়াগো জারদো

সেই লিঙ্কটি রত্ন!
বি

13

কেবল এটিএপআপডেটর ব্যবহার করুন । এটি 1 লাইন, থ্রেড-নিরাপদ এবং দ্রুত। যদি আপনি ব্যবহারকারীর ক্রিয়া ট্র্যাক করতে চান তবে এটিরও প্রতিনিধি পদ্ধতি রয়েছে।

এখানে একটি উদাহরণ:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[ATAppUpdater sharedUpdater] showUpdateWithConfirmation]; // 1 line of code
    // or
    [[ATAppUpdater sharedUpdater] showUpdateWithForce]; // 1 line of code

   return YES;
}

Delegচ্ছিক প্রতিনিধি পদ্ধতি:

- (void)appUpdaterDidShowUpdateDialog;
- (void)appUpdaterUserDidLaunchAppStore;
- (void)appUpdaterUserDidCancel;

1
এটি টেস্টফ্লাইটে বিটা সংস্করণের জন্য কাজ করবে? যদি তা না হয় তবে কি কোনও সরঞ্জাম রয়েছে যা?
লুকাস্জ সিজারউইনস্কি

না এটি করবে না, এটি কেবলমাত্র বর্তমান সংস্করণটিকে অ্যাপস্টোরের সর্বশেষতম সংস্করণের সাথে তুলনা করে।
ইমোলেটিটি

আমরা কি এটি সুইফট দিয়ে ব্যবহার করতে পারি?
জোড়ায়ার

11

এই থ্রেডটিতে পোস্ট করা একটি দুর্দান্ত উত্তর সরলীকৃত । ব্যবহার Swift 4এবং Alamofire

import Alamofire

class VersionCheck {

  public static let shared = VersionCheck()

  func isUpdateAvailable(callback: @escaping (Bool)->Void) {
    let bundleId = Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String
    Alamofire.request("https://itunes.apple.com/lookup?bundleId=\(bundleId)").responseJSON { response in
      if let json = response.result.value as? NSDictionary, let results = json["results"] as? NSArray, let entry = results.firstObject as? NSDictionary, let versionStore = entry["version"] as? String, let versionLocal = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
        let arrayStore = versionStore.split(separator: ".")
        let arrayLocal = versionLocal.split(separator: ".")

        if arrayLocal.count != arrayStore.count {
          callback(true) // different versioning system
        }

        // check each segment of the version
        for (key, value) in arrayLocal.enumerated() {
          if Int(value)! < Int(arrayStore[key])! {
            callback(true)
          }
        }
      }
      callback(false) // no new version or failed to fetch app store version
    }
  }

}

এবং তারপরে এটি ব্যবহার করতে:

VersionCheck.shared.isUpdateAvailable() { hasUpdates in
  print("is update available: \(hasUpdates)")
}

2
আমার অ্যাপ্লিকেশনটি স্টোরে লাইভ তবে একই এপিআই সংস্করণ তথ্য ফেরত পাবে না। প্রতিক্রিয়া:{ "resultCount":0, "results": [] }
টেকনারড

সংস্করণ তুলনার জন্য কেবল একটি নোট যুক্ত করুন, আমি পছন্দ করবো, সার্ভার ভার্সন = "২. local" কে স্থানীয় সংস্করণ = "২. let.৫" চলুন, ইউজডেটএভিলিয়েশন = সার্ভার ভার্সন ডটকম (স্থানীয় সংস্করণ, বিকল্পগুলি:। সংখ্যাগত) == .অর্ডারড পরিবর্তনের পরিবর্তে ডিজাইনেডিং করা উচিত। খালি দিয়ে
চিতু

@ ছাইতু এই পরামর্শের জন্য আপনাকে ধন্যবাদ। আমি
কোডটির

9

অনুপ গুপ্তার কাছ থেকে সুইফট 4 কোড আপডেট হয়েছে

আমি এই কোডটিতে কিছু পরিবর্তন করেছি । এখন ফাংশনগুলি একটি পটভূমি সারি থেকে কল করা হয়, যেহেতু সংযোগটি ধীর হতে পারে এবং তাই মূল থ্রেডটি ব্লক করে।

আমি সিএফবান্ডেলনামকে alচ্ছিকও করেছিলাম, যেহেতু উপস্থাপিত সংস্করণটিতে "সিএফবান্ডেলডিসপ্লিনাম" ছিল যা সম্ভবত আমার সংস্করণে কার্যকর হয়নি work সুতরাং এখন এটি উপস্থিত না থাকলে এটি ক্রাশ হবে না তবে সতর্কতার মধ্যে অ্যাপের নামটি প্রদর্শন করবে না।

import UIKit

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

class LookupResult: Decodable {
    var results: [AppInfo]
}

class AppInfo: Decodable {
    var version: String
    var trackViewUrl: String
}

class AppUpdater: NSObject {

    private override init() {}
    static let shared = AppUpdater()

    func showUpdate(withConfirmation: Bool) {
        DispatchQueue.global().async {
            self.checkVersion(force : !withConfirmation)
        }
    }

    private  func checkVersion(force: Bool) {
        let info = Bundle.main.infoDictionary
        if let currentVersion = info?["CFBundleShortVersionString"] as? String {
            _ = getAppInfo { (info, error) in
                if let appStoreAppVersion = info?.version{
                    if let error = error {
                        print("error getting app store version: ", error)
                    } else if appStoreAppVersion == currentVersion {
                        print("Already on the last app version: ",currentVersion)
                    } else {
                        print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
                        DispatchQueue.main.async {
                            let topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
                            topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
                        }
                    }
                }
            }
        }
    }

    private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
        guard let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
            let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
                DispatchQueue.main.async {
                    completion(nil, VersionError.invalidBundleInfo)
                }
                return nil
        }
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                if let error = error { throw error }
                guard let data = data else { throw VersionError.invalidResponse }
                let result = try JSONDecoder().decode(LookupResult.self, from: data)
                guard let info = result.results.first else { throw VersionError.invalidResponse }

                completion(info, nil)
            } catch {
                completion(nil, error)
            }
        }
        task.resume()
        return task
    }
}

extension UIViewController {
    @objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
        let appName = Bundle.appName()

        let alertTitle = "New Version"
        let alertMessage = "\(appName) Version \(Version) is available on AppStore."

        let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)

        if !Force {
            let notNowButton = UIAlertAction(title: "Not Now", style: .default)
            alertController.addAction(notNowButton)
        }

        let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
            guard let url = URL(string: AppURL) else {
                return
            }
            if #available(iOS 10.0, *) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                UIApplication.shared.openURL(url)
            }
        }

        alertController.addAction(updateButton)
        self.present(alertController, animated: true, completion: nil)
    }
}
extension Bundle {
    static func appName() -> String {
        guard let dictionary = Bundle.main.infoDictionary else {
            return ""
        }
        if let version : String = dictionary["CFBundleName"] as? String {
            return version
        } else {
            return ""
        }
    }
}

নিশ্চিতকরণ বোতামটি যুক্ত করার জন্য আমি এই কলটি করছি:

AppUpdater.shared.showUpdate(withConfirmation: true)

বা ফোর্স আপডেট অপশন চালু রাখতে এটিকে ডাকতে কল করুন:

AppUpdater.shared.showUpdate(withConfirmation: false)

এটি পরীক্ষা করার জন্য কোনও ধারণা? যদি এটি সঠিকভাবে কাজ করতে ব্যর্থ হয় তবে এটিকে ডিবাগ করার একমাত্র উপায় হ'ল অ্যাপ স্টোরের চেয়ে কোনও পুরানো সংস্করণ ডিবাগ করা।
ডেভিড রেক্টর

2
আহ, প্রশ্ন কোন দিন মনে করবেন না। আমি কেবল আমার স্থানীয় সংস্করণটিকে "পুরানো" হিসাবে পরিবর্তন করতে পারি।
ডেভিড রেক্টর

আমি আপনার কোড @ ভাস্কো দ্বারা মুগ্ধ। কেবল একটি সহজ প্রশ্ন, আপনি কেন সেই url এ https এর পরিবর্তে 'HTTP' ব্যবহার করেছেন?
মাস্টার এজেন্টএক্স

@ ভাস্কো এই সমাধানটি ভাগ করে নেওয়ার জন্য অনেক ধন্যবাদ! আমি এটি পছন্দ করি :) আপনি কেন ব্যবহার করবেন না: ব্যাকগ্রাউন্ডের অনুরোধটি অর্জনের জন্য URL সেশনের জন্য কনফিগারেশন = ইউআরএলসেশন কনফিগারেশন.ব্যাকগ্রাউন্ড (আইডেন্টিফায়ার: "com.example.MyExample.background") দিন?
এমসি_প্লেচ্রাম

আপনি জোর আনার্পিং থেকে মুক্তি পেতে পারেন, যেমন আপনি যদি আগে থেকেই পরীক্ষা করে থাকেন তবে অ্যাপস্টোর অ্যাপ্লিকেশন = তথ্য? .ট্রাকশন এবং ট্র্যাকরুলের জন্য একই কিনা।
এমসি_প্লেট্রাম

7

এখানে সুইফট 4 এবং জনপ্রিয় অ্যালামোফায়ার লাইব্রেরি ব্যবহার করে আমার সংস্করণ দেওয়া আছে (আমি যাইহোক এটি আমার অ্যাপ্লিকেশনগুলিতে ব্যবহার করি)। অনুরোধটি অ্যাসিঙ্ক্রোনাস when এবং হয়ে গেলে আপনি বিজ্ঞপ্তি দিতে একটি কলব্যাক পাস করতে পারেন।

import Alamofire

class VersionCheck {

    public static let shared = VersionCheck()

    var newVersionAvailable: Bool?
    var appStoreVersion: String?

    func checkAppStore(callback: ((_ versionAvailable: Bool?, _ version: String?)->Void)? = nil) {
        let ourBundleId = Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String
        Alamofire.request("https://itunes.apple.com/lookup?bundleId=\(ourBundleId)").responseJSON { response in
            var isNew: Bool?
            var versionStr: String?

            if let json = response.result.value as? NSDictionary,
               let results = json["results"] as? NSArray,
               let entry = results.firstObject as? NSDictionary,
               let appVersion = entry["version"] as? String,
               let ourVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
            {
                isNew = ourVersion != appVersion
                versionStr = appVersion
            }

            self.appStoreVersion = versionStr
            self.newVersionAvailable = isNew
            callback?(isNew, versionStr)
        }
    }
}

ব্যবহার এর মতো সহজ:

VersionCheck.shared.checkAppStore() { isNew, version in
        print("IS NEW VERSION AVAILABLE: \(isNew), APP STORE VERSION: \(version)")
    }

1
আমাদের ভার্সন ব্যবহারে সমস্যা! = অ্যাপ ভার্সনটি হ'ল অ্যাপ স্টোর পর্যালোচনা দলটি অ্যাপটির নতুন সংস্করণটি পরীক্ষা করে এমনটি ট্রিগার করে। আমরা সেই সংস্করণটির স্ট্রিংগুলিকে সংখ্যায় রূপান্তর করি এবং তারপরে হ'ল নতুন = অ্যাপ ভার্সন> আমাদের সংস্করণ।
বুড়িডিনো

@ বুডিডিনো আপনি ঠিক বলেছেন, আমি আলামোফায়ার ব্যবহার করে সাধারণ পন্থাটি দেখিয়েছি। আপনি কীভাবে সংস্করণটি ব্যাখ্যা করবেন তা সম্পূর্ণভাবে আপনার অ্যাপ এবং সংস্করণ কাঠামোর উপর নির্ভরশীল।
উত্তর ক্যাপ্টেন

সংস্করণ তুলনায় কেবল একটি নোট যুক্ত করুন, আমি পছন্দ করবো, সার্ভার ভার্সন = "২. let" কে স্থানীয় সংস্করণ = "২. let.৫" যাক যাক ইউপিডেটএভিলিয়েশন = সার্ভার ভার্সন ডট কম (স্থানীয় সংস্করণ, বিকল্পগুলি:। সংখ্যাগত) ==। সমমানের সাথে তুলনা না করে অর্ডারডেস্যান্ডিং
চিতু

6

আমি কি এই ছোট লাইব্রেরিটির পরামর্শ দিতে পারি: https://github.com/nicklockwood/iVersion

এর উদ্দেশ্য বিজ্ঞপ্তিগুলি ট্রিগার করতে দূরবর্তী plists পরিচালনা পরিচালনা সহজ করা to


3
আপনি কোথাও প্লাস্ট ফাইলের হোস্টিংয়ের পরিবর্তে সংস্করণ নম্বরটির জন্য অ্যাপ স্টোরটি সরাসরি পরীক্ষা করতে পারেন। এই উত্তরটি দেখুন: স্ট্যাকওভারফ্লো.com
স্টিভ মোসার

1
iVersion এখন অ্যাপ স্টোর সংস্করণটি স্বয়ংক্রিয়ভাবে ব্যবহার করে - আপনি যদি আইটিউনসগুলিতে বিভিন্ন প্রকাশের নোটগুলি নির্দিষ্ট করতে চান তবে প্লিস্টটি isচ্ছিক, তবে আপনার এটি ব্যবহার করার দরকার নেই।
নিক লকউড

1
এই কোডটি কিছু উন্নতি করতে পারে তবে অন্যান্য উত্তরগুলির চেয়ে অনেক বেশি ভাল যা একটি সিঙ্ক্রোনাস অনুরোধ পাঠায়। তবুও, এটি থ্রেডিংয়ের উপায়টি খারাপ শৈলী। আমি গিতুবকে ইস্যু করব।
uliwitness 10

প্রকল্পটি এখন
অবচয় করা

5

সুইফট ৩.১

func needsUpdate() -> Bool {
    let infoDictionary = Bundle.main.infoDictionary
    let appID = infoDictionary!["CFBundleIdentifier"] as! String
    let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(appID)")
    guard let data = try? Data(contentsOf: url) else {
      print("There is an error!")
      return false;
    }
    let lookup = (try? JSONSerialization.jsonObject(with: data! , options: [])) as? [String: Any]
    if let resultCount = lookup!["resultCount"] as? Int, resultCount == 1 {
        if let results = lookup!["results"] as? [[String:Any]] {
            if let appStoreVersion = results[0]["version"] as? String{
                let currentVersion = infoDictionary!["CFBundleShortVersionString"] as? String
                if !(appStoreVersion == currentVersion) {
                    print("Need to update [\(appStoreVersion) != \(currentVersion)]")
                    return true
                }
            }
        }
    }
    return false
}

আপনার কোনও ইন্টারনেট সংযোগ না থাকলে এই ক্রাশ হয়। তথ্য = চেষ্টা করা যাক? ডেটা (বিষয়বস্তু: ইউআরএল!) শূন্য ফিরে আসবে, এবং পরবর্তী লাইনে আপনি ডেটা করবেন!
জোরিস ম্যানস

thx @ জরিসমানস আমি কোনও ইন্টারনেট সংযোগ বিপর্যয়ের জন্য এটি আপডেট করব
কাসেম ইটানি

এটি করবেন না। ব্যবহার URLSession
জাল

4

এই উত্তরটি ড্যাটিন্সের উত্তর https://stackoverflow.com/a/25210143/2735358 এ পরিবর্তন ।

ড্যাটিনিকের মজাদার স্ট্রিং তুলনা করে সংস্করণটির তুলনা করে। সুতরাং, এটি এর চেয়ে বড় বা তার চেয়ে কম সংস্করণের তুলনা করবে না।

তবে, এই পরিবর্তিত ফাংশনটি এনএসনুমারিক অনুসন্ধান (সংখ্যার তুলনা) দ্বারা সংস্করণটির সাথে তুলনা করে

- (void)checkForUpdateWithHandler:(void(^)(BOOL isUpdateAvailable))updateHandler {

    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *appID = infoDictionary[@"CFBundleIdentifier"];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
    NSLog(@"iTunes Lookup URL for the app: %@", url.absoluteString);

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *theTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:url]
                                               completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

                                                   NSDictionary *lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                                                   NSLog(@"iTunes Lookup Data: %@", lookup);
                                                   if (lookup && [lookup[@"resultCount"] integerValue] == 1){
                                                       NSString *appStoreVersion = lookup[@"results"][0][@"version"];
                                                       NSString *currentVersion = infoDictionary[@"CFBundleShortVersionString"];

                                                       BOOL isUpdateAvailable = [appStoreVersion compare:currentVersion options:NSNumericSearch] == NSOrderedDescending;
                                                       if (isUpdateAvailable) {
                                                           NSLog(@"\n\nNeed to update. Appstore version %@ is greater than %@",appStoreVersion, currentVersion);
                                                       }
                                                       if (updateHandler) {
                                                           updateHandler(isUpdateAvailable);
                                                       }
                                                   }
                                               }];
    [theTask resume];
}

ব্যবহার করুন:

[self checkForUpdateWithHandler:^(BOOL isUpdateAvailable) {
    if (isUpdateAvailable) {
        // show alert
    }
}];

3
এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी

আমরা অন্যথায় নির্দিষ্ট না করে এনএসআরএলসেশন স্বয়ংক্রিয়ভাবে পটভূমির থ্রেডগুলিতে কাজ করে।
সেবাস্তিয়ান দ্বার্নিক

4

আমি অ্যাপ্লিকেশন আপডেট চেক করার বিভিন্ন উপায় দেখেছি। সুতরাং অনেক উত্তরের উপর ভিত্তি করে আমি সেগুলিকে মিশ্রিত করি এবং আমার সমাধানটি তৈরি করি যা গিটহাবের উপর উপলভ্য আছে যদি কোনও আপডেটের প্রয়োজন হয় তবে দয়া করে আমাকে জানান। এই কোডটি সুইফট 4 এর জন্য

এই কোডটিতে গিটহাব লিঙ্ক। https://github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater

   import UIKit

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

class LookupResult: Decodable {
    var results: [AppInfo]
}

class AppInfo: Decodable {
    var version: String
    var trackViewUrl: String
    //let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
    // You can add many thing based on "http://itunes.apple.com/lookup?bundleId=\(identifier)"  response
    // here version and trackViewUrl are key of URL response
    // so you can add all key beased on your requirement.

}

class ArgAppUpdater: NSObject {
    private static var _instance: ArgAppUpdater?;

    private override init() {

    }

    public static func getSingleton() -> ArgAppUpdater {
        if (ArgAppUpdater._instance == nil) {
            ArgAppUpdater._instance = ArgAppUpdater.init();
        }
        return ArgAppUpdater._instance!;
    }

    private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
        guard let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
            let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
                DispatchQueue.main.async {
                    completion(nil, VersionError.invalidBundleInfo)
                }
                return nil
        }
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                if let error = error { throw error }
                guard let data = data else { throw VersionError.invalidResponse }

                print("Data:::",data)
                print("response###",response!)

                let result = try JSONDecoder().decode(LookupResult.self, from: data)

                let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)

                print("dictionary",dictionary!)


                guard let info = result.results.first else { throw VersionError.invalidResponse }
                print("result:::",result)
                completion(info, nil)
            } catch {
                completion(nil, error)
            }
        }
        task.resume()

        print("task ******", task)
        return task
    }
    private  func checkVersion(force: Bool) {
        let info = Bundle.main.infoDictionary
        let currentVersion = info?["CFBundleShortVersionString"] as? String
        _ = getAppInfo { (info, error) in

            let appStoreAppVersion = info?.version

            if let error = error {
                print(error)



            }else if appStoreAppVersion!.compare(currentVersion!, options: .numeric) == .orderedDescending {
                //                print("needs update")
               // print("hiiii")
                DispatchQueue.main.async {
                    let topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!

                    topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
            }

            }
        }


    }

    func showUpdateWithConfirmation() {
        checkVersion(force : false)


    }

    func showUpdateWithForce() {
        checkVersion(force : true)
    }



}

extension UIViewController {


    fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
        print("AppURL:::::",AppURL)

        let bundleName = Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String;
        let alertMessage = "\(bundleName) Version \(Version) is available on AppStore."
        let alertTitle = "New Version"


        let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)


        if !Force {
            let notNowButton = UIAlertAction(title: "Not Now", style: .default) { (action:UIAlertAction) in
                print("Don't Call API");


            }
            alertController.addAction(notNowButton)
        }

        let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
            print("Call API");
            print("No update")
            guard let url = URL(string: AppURL) else {
                return
            }
            if #available(iOS 10.0, *) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                UIApplication.shared.openURL(url)
            }

        }

        alertController.addAction(updateButton)
        self.present(alertController, animated: true, completion: nil)
    }
}

পুনঃপ্রকাশ: https://stackoverflow.com/a/48810541/5855888 এবং https://github.com/emotality/ATappUpdater

শুভ কোডিং 👍 😊


@Rob দয়া করে চেক করুন GitHub লিংক github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater
অনুপ গুপ্ত

2

এখানে একটি দ্রুত পদক্ষেপ যা কিছু উদ্দেশ্য-সি উত্তরগুলির পরামর্শ অনুসারে কাজ করে। স্পষ্টতই, আপনি একবার অ্যাপ স্টোর জেএসওএন থেকে তথ্য পাবেন, আপনি চাইলে রিলিজ নোটগুলি বের করতে পারেন।

func appUpdateAvailable(storeInfoURL: String) -> Bool
{
    var upgradeAvailable = false

    // Get the main bundle of the app so that we can determine the app's version number
    let bundle = NSBundle.mainBundle()
    if let infoDictionary = bundle.infoDictionary {
        // The URL for this app on the iTunes store uses the Apple ID for the  This never changes, so it is a constant
        let urlOnAppStore = NSURL(string: storeInfoURL)
        if let dataInJSON = NSData(contentsOfURL: urlOnAppStore!) {
            // Try to deserialize the JSON that we got
            if let lookupResults = try? NSJSONSerialization.JSONObjectWithData(dataInJSON, options: NSJSONReadingOptions()) {
                // Determine how many results we got. There should be exactly one, but will be zero if the URL was wrong
                if let resultCount = lookupResults["resultCount"] as? Int {
                    if resultCount == 1 {
                        // Get the version number of the version in the App Store
                        if let appStoreVersion = lookupResults["results"]!![0]["version"] as? String {
                            // Get the version number of the current version
                            if let currentVersion = infoDictionary["CFBundleShortVersionString"] as? String {
                                // Check if they are the same. If not, an upgrade is available.
                                if appStoreVersion != currentVersion {
                                    upgradeAvailable = true                      
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return upgradeAvailable
}

storeInfoURL অ্যাপস্টোর এ অ্যাপ্লিকেশন url হয়?
iamthevoid

@ মারিও হেন্ড্রিক্স এটি দ্রুত 3 এ কাজ করছে না এটি কিছু ত্রুটি ছুঁড়েছে। আপনি দয়া করে সুইফট 3 এর জন্য আপডেট করতে পারেন?
জর্জ আসদা

এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी 10

2

আপনি যদি এনএসইউরআরউয়েস্টে সামগ্রী প্রকার সেট না করে থাকেন তবে নিশ্চিতভাবেই আপনি প্রতিক্রিয়া পাবেন না, তাই নীচের কোডটি চেষ্টা করুন এটি আমার পক্ষে ভাল কাজ করে। আশা করি এটা সাহায্য করবে....

-(BOOL) isUpdateAvailable{
    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString* appID = infoDictionary[@"CFBundleIdentifier"];
    NSString *urlString = [NSString stringWithFormat:@"https://itunes.apple.com/lookup?bundleId=%@",appID];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:urlString]];
    [request setHTTPMethod:@"GET"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSURLResponse *response;
    NSError *error;
    NSData *data = [NSURLConnection  sendSynchronousRequest:request returningResponse: &response error: &error];
    NSError *e = nil;
    NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error: &e];

    self.versionInAppStore = [[[jsonDict objectForKey:@"results"] objectAtIndex:0] objectForKey:@"version"];

    self.localAppVersion = infoDictionary[@"CFBundleShortVersionString"];

    if ([self.versionInAppStore compare:self.localAppVersion options:NSNumericSearch] == NSOrderedDescending) {
        // currentVersion is lower than the version
        return YES;
    }
    return NO;
}

এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी 10

2

হাইব্রিড অ্যাপ্লিকেশন পিওভি থেকে আসছে, এটি একটি জাভাস্ক্রিপ্ট উদাহরণ, আমার মূল মেনুতে আমার একটি আপডেট উপলব্ধ পাদচরণ রয়েছে। যদি কোনও আপডেট উপলব্ধ থাকে (যেমন, কনফিগার ফাইলের মধ্যে আমার সংস্করণ নম্বরটি পুনরুদ্ধার করা সংস্করণের চেয়ে কম, ফুটার প্রদর্শন করুন) এটি ব্যবহারকারীকে অ্যাপ স্টোরের দিকে পরিচালিত করবে, যেখানে ব্যবহারকারী আপডেট আপডেট বোতামটি ক্লিক করতে পারেন।

আমি কী নতুন ডেটা পেয়েছি (যেমন রিলিজ নোটস) এবং এটিকে এই সংস্করণে প্রথমবারের মতো লগইন করার সময় কোনও মডেলে প্রদর্শন করে।

আপডেট উপলভ্য পদ্ধতিটি আপনার পছন্দ হিসাবে প্রায়শই চালানো যেতে পারে। প্রতিবার ব্যবহারকারী হোম স্ক্রিনে নেভিগেট করে আমার চালানো হয়।

function isUpdateAvailable() {
        $.ajax('https://itunes.apple.com/lookup?bundleId=BUNDLEID', {
            type: "GET",
            cache: false,
            dataType: 'json'
        }).done(function (data) {
            _isUpdateAvailable(data.results[0]);
        }).fail(function (jqXHR, textStatus, errorThrown) {
            commsErrorHandler(jqXHR, textStatus, false);
        });

}

কলব্যাক: অ্যাপলের একটি এপিআই রয়েছে, তাই এটি পাওয়া খুব সহজ

function isUpdateAvailable_iOS (data) {
    var storeVersion = data.version;
    var releaseNotes = data.releaseNotes;
    // Check store Version Against My App Version ('1.14.3' -> 1143)
    var _storeV = parseInt(storeVersion.replace(/\./g, ''));
    var _appV = parseInt(appVersion.substring(1).replace(/\./g, ''));
    $('#ft-main-menu-btn').off();
    if (_storeV > _appV) {
        // Update Available
        $('#ft-main-menu-btn').text('Update Available');
        $('#ft-main-menu-btn').click(function () {
           // Open Store      
           window.open('https://itunes.apple.com/us/app/appname/idUniqueID', '_system');
        });

    } else {
        $('#ft-main-menu-btn').html('&nbsp;');
        // Release Notes
        settings.updateReleaseNotes('v' + storeVersion, releaseNotes);
    }
}

2

সতর্কতা: প্রদত্ত উত্তর অধিকাংশই সিঙ্ক্রোনাস URL- টি উদ্ধার করা (ব্যবহার -dataWithContentsOfURL:বা -sendSynchronousRequest:এই খারাপ, যেমন এর মানে হল যে আপনার আবেদন কয়েক মিনিটের জন্য ক্ষমাহীন যদি মোবাইল সংযোগ ড্রপ অনুরোধটি প্রগতিতে রয়েছে থাকবে।। কখনো উপর সিঙ্ক্রোনাস ইন্টারনেট ব্যবহার করেন প্রধান থ্রেড.

সঠিক উত্তরটি অ্যাসিঙ্ক্রোনাস এপিআই ব্যবহার করা:

    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString* appID = infoDictionary[@"CFBundleIdentifier"];
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
    NSURLSession         *  session = [NSURLSession sharedSession];
    NSURLSessionDataTask *  theTask = [session dataTaskWithRequest: [NSURLRequest requestWithURL: url] completionHandler:
    ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
    {
        NSDictionary<NSString*,NSArray*>* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        if ([lookup[@"resultCount"] integerValue] == 1)
        {
            NSString* appStoreVersion = lookup[@"results"].firstObject[@"version"];
           NSString* currentVersion = infoDictionary[@"CFBundleShortVersionString"];

            if ([appStoreVersion compare:currentVersion options:NSNumericSearch] == NSOrderedDescending) {
                // *** Present alert about updating to user ***
            }
        }
    }];
    [theTask resume];

নেটওয়ার্ক সংযোগগুলির জন্য ডিফল্ট সময়সীমা কয়েক মিনিট। আপনি চাইছেন না যে আপনার অ্যাপটি সেই ক্ষেত্রে অকেজো হয়ে উঠবে। এই জাতীয় জিনিস পরীক্ষা করতে, অ্যাপল এর নেটওয়ার্ক লিংক কন্ডিশনার দিয়ে আপনার নেটওয়ার্কিং কোড চালানো কার্যকর is


এই প্রশ্নটি বাঁচিয়ে রাখার জন্য ধন্যবাদ :-)
জীবন

2
func isUpdateAvailable() -> Bool {
    guard
        let info = Bundle.main.infoDictionary,
        let identifier = info["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)"),
        let data = try? Data(contentsOf: url),
        let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any],
        let results = json?["results"] as? [[String: Any]],
        results.count > 0,
        let versionString = results[0]["version"] as? String
        else {
            return false
    }

    return AppVersion(versionString) > AppVersion.marketingVersion
}

সংস্করণ স্ট্রিং তুলনা:

https://github.com/eure/AppVersionMonitor


2

সুইফট 4 এবং 3.2 এর জন্য:

প্রথমত, আমাদের বান্ডিল তথ্য অভিধান থেকে বান্ডেল আইডিটি পাওয়া দরকার, সেটটি ইউএসপেটকে মিথ্যা হিসাবে সেট করুন।

    var isUpdate = false
    guard let bundleInfo = Bundle.main.infoDictionary,
        let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String,
        //let identifier = bundleInfo["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)")
        else{
        print("something wrong")
            completion(false)
        return
       }

তারপরে আইটিউনস থেকে সংস্করণ পাওয়ার জন্য আমাদের urlSession কল করতে হবে।

    let task = URLSession.shared.dataTask(with: url) {
        (data, resopnse, error) in
        if error != nil{
             completion(false)
            print("something went wrong")
        }else{
            do{
                guard let reponseJson = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any],
                let result = (reponseJson["results"] as? [Any])?.first as? [String: Any],
                let version = result["version"] as? String
                else{
                     completion(false)
                    return
                }
                print("Current Ver:\(currentVersion)")
                print("Prev version:\(version)")
                if currentVersion != version{
                    completion(true)
                }else{
                    completion(false)
                }
            }
            catch{
                 completion(false)
                print("Something went wrong")
            }
        }
    }
    task.resume()

সম্পূর্ণ কোডটি এটি পছন্দ করবে:

func checkForUpdate(completion:@escaping(Bool)->()){

    guard let bundleInfo = Bundle.main.infoDictionary,
        let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String,
        //let identifier = bundleInfo["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)")
        else{
        print("some thing wrong")
            completion(false)
        return
       }

    let task = URLSession.shared.dataTask(with: url) {
        (data, resopnse, error) in
        if error != nil{
             completion(false)
            print("something went wrong")
        }else{
            do{
                guard let reponseJson = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any],
                let result = (reponseJson["results"] as? [Any])?.first as? [String: Any],
                let version = result["version"] as? String
                else{
                     completion(false)
                    return
                }
                print("Current Ver:\(currentVersion)")
                print("Prev version:\(version)")
                if currentVersion != version{
                    completion(true)
                }else{
                    completion(false)
                }
            }
            catch{
                 completion(false)
                print("Something went wrong")
            }
        }
    }
    task.resume()
}

তারপরে আমরা ফাংশনটি আমাদের যেকোনও কল করতে পারি।

    checkForUpdate { (isUpdate) in
        print("Update needed:\(isUpdate)")
        if isUpdate{
            DispatchQueue.main.async {
                print("new update Available")
            }
        }
    }

2

সি # সমতুল্য @ ড্যাটিনিকের হিসাবে, অ্যাপল অ্যাপ স্টোর সংস্করণটি অর্জন করার ক্ষেত্রে। উভয় বান্ডিল বা এসেম্বলিআইএনফো ফাইলের সংস্করণ পেতে কোড অন্তর্ভুক্ত।

সম্পাদনা :: দয়া করে urlString এ অন্তর্ভুক্ত অঞ্চলটি "/ আমাদের /" নোট করুন। এই দেশের কোডটি হ্যান্ডেল / সেই অনুযায়ী পরিবর্তন করতে হবে।

string GetAppStoreVersion()
{
    string version = "";

    NSDictionary infoDictionary = NSBundle
        .MainBundle
        .InfoDictionary;

    String appID = infoDictionary["CFBundleIdentifier"].ToString();

    NSString urlString = 
        new NSString(@"http://itunes.apple.com/us/lookup?bundleId=" + appID);
    NSUrl url = new NSUrl(new System.Uri(urlString).AbsoluteUri);

    NSData data = NSData.FromUrl(url);

    if (data == null)
    {
        /* <-- error obtaining data from url --> */
        return "";
    }

    NSError e = null;
    NSDictionary lookup = (NSDictionary)NSJsonSerialization
        .Deserialize(data, NSJsonReadingOptions.AllowFragments, out e);

    if (lookup == null)
    {
        /* <-- error, most probably no internet or bad connectivity --> */
        return "";
    }

    if (lookup["resultCount"].Description.Equals("1"))
    {
        NSObject nsObject = lookup["results"];
        NSString nsString = new NSString("version");
        String line = nsObject
            .ValueForKey(nsString)
            .Description;

        /* <-- format string --> */
        string[] digits = Regex.Split(line, @"\D+");
        for (int i = 0; i < digits.Length; i++)
        {
            if (int.TryParse(digits[i], out int intTest))
            {
                if (version.Length > 0)
                    version += "." + digits[i];
                else
                    version += digits[i];
            }
        }
    }

    return version;
}

string GetBundleVersion()
{
        return NSBundle
            .MainBundle
            .InfoDictionary["CFBundleShortVersionString"]
            .ToString();
}

string GetAssemblyInfoVersion()
{
        var assembly = typeof(App).GetTypeInfo().Assembly;
        var assemblyName = new AssemblyName(assembly.FullName);
        return assemblyName.Version.ToString();
}

2

একক ফাংশন কল দিয়ে এটি ব্যবহার করে দেখুন:

func showAppStoreVersionUpdateAlert(isForceUpdate: Bool) {

    do {
        //Get Bundle Identifire from Info.plist
        guard let bundleIdentifire = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else {
            print("No Bundle Info found.")
            throw CustomError.invalidIdentifires
        }

        // Build App Store URL
        guard let url = URL(string:"http://itunes.apple.com/lookup?bundleId=" + bundleIdentifire) else {
            print("Isse with generating URL.")
            throw CustomError.invalidURL
        }

        let serviceTask = URLSession.shared.dataTask(with: url) { (responseData, response, error) in

            do {
                // Check error
                if let error = error { throw error }
                //Parse response
                guard let data = responseData else { throw CustomError.jsonReading }
                let result = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
                let itunes = ItunesAppInfoItunes.init(fromDictionary: result as! [String : Any])
                print(itunes.results)
                if let itunesResult = itunes.results.first {
                    print("App Store Varsion: ",itunesResult.version)

                    //Get Bundle Version from Info.plist
                    guard let appShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
                        print("No Short Version Info found.")
                        throw CustomError.invalidVersion
                    }

                    if appShortVersion == itunesResult.version {
                        //App Store & Local App Have same Version.
                        print("Same Version at both side")
                    } else {
                        //Show Update alert
                        var message = ""
                        //Get Bundle Version from Info.plist
                        if let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String {
                            message = "\(appName) has new version(\(itunesResult.version!)) available on App Store."
                        } else {
                            message = "This app has new version(\(itunesResult.version!)) available on App Store."
                        }

                        //Show Alert on the main thread
                        DispatchQueue.main.async {
                            self.showUpdateAlert(message: message, appStoreURL: itunesResult.trackViewUrl, isForceUpdate: isForceUpdate)
                        }
                    }
                }
            } catch {
                print(error)
            }
        }
        serviceTask.resume()
    } catch {
        print(error)
    }
}

অ্যাপস্টোর ইউআরএল খুলতে সতর্কতা ফাংশন:

func showUpdateAlert(message : String, appStoreURL: String, isForceUpdate: Bool) {

    let controller = UIAlertController(title: "New Version", message: message, preferredStyle: .alert)

    //Optional Button
    if !isForceUpdate {
        controller.addAction(UIAlertAction(title: "Later", style: .cancel, handler: { (_) in }))
    }

    controller.addAction(UIAlertAction(title: "Update", style: .default, handler: { (_) in
        guard let url = URL(string: appStoreURL) else {
            return
        }
        if #available(iOS 10.0, *) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        } else {
            UIApplication.shared.openURL(url)
        }

    }))

    let applicationDelegate = UIApplication.shared.delegate as? AppDelegate
    applicationDelegate?.window?.rootViewController?.present(controller, animated: true)

}

উপরের ফাংশনটি কীভাবে কল করবেন:

AppStoreUpdate.shared.showAppStoreVersionUpdateAlert(isForceUpdate: false/true)

আরও বিস্তারিত তথ্যের জন্য পূর্ণ কোডের সাথে নীচের লিঙ্কটি চেষ্টা করুন:

AppStoreUpdate.swift

ItunesAppInfoResult.swift

ItunesAppInfoItunes.swift

আমি আশা করি এটি সাহায্য করবে!


1

এই প্রশ্নটি ২০১১ সালে জিজ্ঞাসা করা হয়েছিল, আমি কেবল অ্যাপ স্টোরের অ্যাপ্লিকেশনটির নতুন সংস্করণ পরীক্ষা করার জন্য নয় তবে এটির সম্পর্কে ব্যবহারকারীকে অবহিত করার জন্য কিছু উপায় অনুসন্ধান করার সময় এটি 2018 সালে পেয়েছি।

ছোট গবেষণার পরে আমি এই সিদ্ধান্তে পৌঁছেছি যে যুঁজোর উত্তর (সুইফট 3 সম্পর্কিত) https://stackoverflow.com/a/40939740/1218405 আপনি নিজের কোডে কোডটিতে এটি করতে চাইলে সর্বোত্তম সমাধান is

এছাড়াও আমি গিটহাবের দুটি বড় প্রকল্পের প্রস্তাব দিতে পারি (প্রতিটি 2300+ তারা)

সাইরেনের জন্য উদাহরণ (AppDelegate.swift)

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

      let siren = Siren.shared
      siren.checkVersion(checkType: .immediately)

      return true
    }
  • আপনি নতুন সংস্করণ সম্পর্কে বিভিন্ন ধরণের সতর্কতাও প্রদর্শন করতে পারেন (সংস্করণটি এড়িয়ে যেতে বা ব্যবহারকারীকে আপডেট করতে বাধ্য করা)
  • সংস্করণ চেকটি কত ঘন ঘন হওয়া উচিত তা আপনি নির্দিষ্ট করতে পারেন (দৈনিক / সাপ্তাহিক / তাত্ক্ষণিক)
  • অ্যাপ স্টোর সতর্কতার জন্য প্রকাশিত হওয়া নতুন সংস্করণের কত দিন পরে আপনি তা নির্দিষ্ট করতে পারেন

বিদ্যমান উত্তরের লিঙ্কগুলি উত্তর নয়। অতিরিক্তভাবে, লাইব্রেরিতে লিঙ্কগুলিও উত্তর নয় যতক্ষণ না আপনি স্পষ্টভাবে যোগ না করে লিঙ্কটি আপনার উত্তরে কীভাবে উত্তর দেয় (কোড উদাহরণগুলি ইত্যাদি যোগ করুন)।
জাল

1

সুইফট 4

আমরা JSONDecoderপ্রতিক্রিয়াটি পার্স করতে নতুনটি ব্যবহার করতে পারি itunes.apple.com/lookup Decodable শ্রেণী structs মধ্যে দিয়ে এবং এটি প্রতিনিধিত্ব:

class LookupResult: Decodable {
    var results: [AppInfo]
}

class AppInfo: Decodable {
    var version: String
}

আমাদের বা অন্য কোনও সম্পত্তির AppInfoপ্রয়োজন হলে আমরা releaseNotesঅন্যান্য সম্পত্তিও যুক্ত করতে পারি।

এখন আমরা ব্যবহার করে একটি অ্যাসিঙ্ক অনুরোধ করতে পারি URLSession :

func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
    guard let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
          let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
            DispatchQueue.main.async {
                completion(nil, VersionError.invalidBundleInfo)
            }
            return nil
    }
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        do {
            if let error = error { throw error }
            guard let data = data else { throw VersionError.invalidResponse }
            let result = try JSONDecoder().decode(LookupResult.self, from: data)
            guard let info = result.results.first else { throw VersionError.invalidResponse }

            completion(info, nil)
        } catch {
            completion(nil, error)
        }
    }
    task.resume()
    return task
}

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

এই ফাংশনটি একটি সমাপ্তি ক্লোজার পায় যা আমাদের অনুরোধটি শেষ হয়ে গেলে ডাকা হবে এবং URLSessionDataTaskআমাদের অনুরোধটি বাতিল করতে হবে এমন পরিস্থিতিতে ফিরে আসে এবং এটি এইরকম বলা যেতে পারে:

func checkVersion() {
    let info = Bundle.main.infoDictionary
    let currentVersion = info?["CFBundleShortVersionString"] as? String
    _ = getAppInfo { (info, error) in
        if let error = error {
            print(error)
        } else if info?.version == currentVersion {
            print("updated")
        } else {
            print("needs update")
        }
    }
}

আপনি এই কোডটি কোথায় রেখেছিলেন? দ্বিতীয়টি দেখুন যে আপনি লিকআপআরসাল্ট এবং অ্যাপআইএনফোটিকে ডিকোডেবলের জন্য সেট করেছেন তবে আমি সেগুলি কোথাও সংরক্ষিত দেখতে পাচ্ছি না। আমি এখানে কি মিস করছি?
jessi

আপনার কি ডিক্লেয়ার LookupResultএবং AppInfoশ্রেণীর বাঞ্ছনীয় একটি পৃথক ফাইলে আপনার প্রকল্পের কোথাও: তারা ব্যবহার করা হয় যখন আপনি প্রতিক্রিয়া ডিকোড: JSONDecoder().decode(LookupResult.self, from: data)এবং তারা সংস্করণ চিহ্নকারী পংক্তির ধারণ
Juanjo

আপনার উত্তরের ভিত্তিতে আমি আপনার কোড ব্যবহার করে একটি ফাইল তৈরি করেছি দয়া করে আইওএস-সুইফট-আরগঅ্যাপআপটার
অনুপ গুপ্ত

@ জেসি দয়া করে গীটহাবের উপর আমার কোডটি পরীক্ষা করুন আমি সেখানে আপনার সমাধান পোস্ট করেছি
অনুপ গুপ্ত

0

আমার কোড প্রস্তাব। @ ড্যাটিনক এবং @ মারিও-হেন্ড্রিক্সের উত্তরের ভিত্তিতে

আপনার অবশ্যই অবশ্যই dlog_Errorআপনার লগিং ফানক কলটির সাথে প্রতিস্থাপন করা উচিত ।

এই জাতীয় কোড কাঠামোর কোনও ত্রুটি ঘটলে আপনার অ্যাপ্লিকেশনটিকে ক্রাশ হওয়া থেকে রোধ করা উচিত। আনার জন্য appStoreAppVersionএটি জরুরী নয়, এবং মারাত্মক ত্রুটির দিকে পরিচালিত করা উচিত নয়। এবং তবুও, এই জাতীয় কোড কাঠামোর সাথে, আপনি এখনও আপনার অ-মারাত্মক ত্রুটি লগড পেয়ে যাবেন।

class func appStoreAppVersion() -> String?
{
    guard let bundleInfo = NSBundle.mainBundle().infoDictionary else {
        dlog_Error("Counldn't fetch bundleInfo.")
        return nil
    }
    let bundleId = bundleInfo[kCFBundleIdentifierKey as String] as! String
    // dbug__print("bundleId = \(bundleId)")

    let address = "http://itunes.apple.com/lookup?bundleId=\(bundleId)"
    // dbug__print("address = \(address)")

    guard let url = NSURLComponents.init(string: address)?.URL else {
        dlog_Error("Malformed internet address: \(address)")
        return nil
    }
    guard let data = NSData.init(contentsOfURL: url) else {
        if Util.isInternetAvailable() {
            dlog_MajorWarning("Web server request failed. Yet internet is reachable. Url was: \(address)")
        }// else: internet is unreachable. All ok. It is of course impossible to fetch the appStoreAppVersion like this.
        return nil
    }
    // dbug__print("data.length = \(data.length)")

    if data.length < 100 { //: We got 42 for a wrong address. And aproximately 4684 for a good response
        dlog_MajorWarning("Web server message is unexpectedly short: \(data.length) bytes")
    }

    guard let response = try? NSJSONSerialization.JSONObjectWithData(data, options: []) else {
        dlog_Error("Failed to parse server response.")
        return nil
    }
    guard let responseDic = response as? [String: AnyObject] else {
        dlog_Error("Not a dictionary keyed with strings. Response with unexpected format.")
        return nil
    }
    guard let resultCount = responseDic["resultCount"] else {
        dlog_Error("No resultCount found.")
        return nil
    }
    guard let count = resultCount as? Int else { //: Swift will handle NSNumber.integerValue
        dlog_Error("Server response resultCount is not an NSNumber.integer.")
        return nil
    }
    //:~ Determine how many results we got. There should be exactly one, but will be zero if the URL was wrong
    guard count == 1 else {
        dlog_Error("Server response resultCount=\(count), but was expected to be 1. URL (\(address)) must be wrong or something.")
        return nil
    }
    guard let rawResults = responseDic["results"] else {
        dlog_Error("Response does not contain a field called results. Results with unexpected format.")
        return nil
    }
    guard let resultsArray = rawResults as? [AnyObject] else {
        dlog_Error("Not an array of results. Results with unexpected format.")
        return nil
    }
    guard let resultsDic = resultsArray[0] as? [String: AnyObject] else {
        dlog_Error("Not a dictionary keyed with strings. Results with unexpected format.")
        return nil
    }
    guard let rawVersion = resultsDic["version"] else {
        dlog_Error("The key version is not part of the results")
        return nil
    }
    guard let versionStr = rawVersion as? String else {
        dlog_Error("Version is not a String")
        return nil
    }
    return versionStr.e_trimmed()
}

extension String {
    func e_trimmed() -> String
    {
        return stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
    }
}

1
এই উত্তরটি তার অনুরোধটিকে সুসংগতভাবে তৈরি করে। এর অর্থ একটি খারাপ সংযোগ সহ, অনুরোধটি ফিরে না আসা পর্যন্ত আপনার অ্যাপ্লিকেশনটি কয়েক মিনিটের জন্য অকেজো হতে পারে।
uliw साक्षी

-1

দ্রুত 3 এর জন্য আপডেট হয়েছে:

আপনি যদি সহজ কোডের নীচে ব্যবহার করা আপনার অ্যাপ্লিকেশনটির বর্তমান সংস্করণটি পরীক্ষা করতে চান:

 let object = Bundle.main.infoDictionary?["CFBundleShortVersionString"]

  let version = object as! String
  print("version: \(version)")
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.